Desktop architecture
The desktop is a React 19 single-page app in desktop/ (Vite 8, TypeScript, zustand, TanStack Query, React Aria Components). It talks to the daemon through the generated REST client and one WebSocket, runs without elevated rights, and hosts both the built-in core apps and third-party apps in sandboxed windows.
Overview
Section titled “Overview”- Stack. React 19 with the React Compiler (no hand-written memoisation), Vite 8, zustand for the window manager, TanStack Query for REST data, i18next with ICU messages for texts.
- Source folders in
desktop/src:shell,wm,desktop,items,apps,realtime,api,platform,theme,design,errors,auth,installer,networkandui. - Startup (
main.tsx): apply the stored theme and appearance, create the query client, load the translations, render the app. - Loading path. The sign-in screen is a small chunk. The desktop chunk loads when you sign in (or immediately when a session hint exists), and the sign-in screen prefetches it. The desktop is deliberately not behind
lazy()andSuspense. Each core app loads on first use, never at sign-in. - Installer. The installer interface is loaded only on the live system.
- Design gallery.
desktop/design.html(pnpm design, port 5174) shows every screen for layout checks. It is for development only. - Dev server. It proxies
/apiand/wsto the daemon and rewrites theOriginheader of WebSocket upgrades, because the daemon accepts only its own origin. - Secure contexts. The desktop works on plain HTTP by address. ESLint refuses
crypto.randomUUID,crypto.subtle, the Clipboard API, service workers, WebAuthn, notifications,window.openand similar APIs outsidedesktop/src/platform/browser.ts, which offers fallbacks (randomId,copyText,openInNewTab). - Texts. ESLint also refuses string literals in JSX (
i18next/no-literal-string) and imports of icon libraries other than throughui/icons.ts.
Shell and window manager
Section titled “Shell and window manager”The shell (desktop/src/shell) has the taskbar, the Pithyx menu (start menu), app switcher, windows, wallpaper, notifications, the transfers button and sticky notes. The taskbar can sit at the bottom, top, left or right and is saved with the layout.
The window manager is a zustand store (useDesktop). Every change goes through dispatch(action) and a pure reduce().
- Size classes by viewport width: compact below 600 px, medium from 600 to 1023 px, expanded from 1024 px. Workspaces of 2400 px or more offer snapping to thirds and two-thirds.
- Placement of a window is free, maximized or snapped (left, right, thirds, two-thirds).
- Limit: 50 windows. The daemon enforces the same limit.
- Instances. An app declares
singleormulti. Opening asingleapp again focuses the user’s window on this device and passes launch arguments. - Keyboard. All window shortcuts use Alt+Shift because browsers and operating systems do not claim it: Space for the window menu, Left and Right to snap, Up to maximize, Down to restore or minimize, Page Up and Page Down to cycle windows.
- Layout memory. The layout (windows, positions, order, taskbar) is saved per user and device in the daemon (
PUT /api/desktop/layoutwithIf-Match), debounced by 1 second, only by the visible tab, flushed when the page is hidden. Windows of apps you may no longer open are skipped on restore. Limits: 50 windows, 16 KB of window arguments, 48 KB for the whole layout. “Get desktop from” copies the layout of another device. - Appearance. Accent and wallpaper are saved per user and shared across devices; the light or dark choice is saved per browser.
- Desktop items (shortcuts and notes) are saved per user through
/api/desktop/items.
Core apps
Section titled “Core apps”Core apps are listed in desktop/src/apps/registry.tsx; their ids are in apps/ids.ts and mirrored in the daemon (Apps/CoreApps.cs).
| App id | Name | Who sees it | Instances |
|---|---|---|---|
org.pithyx.files |
Files | granted users | multi |
org.pithyx.storage |
Storage | administrators | single |
org.pithyx.settings |
Settings | administrators | single |
org.pithyx.monitor |
Resource Monitor | administrators | single |
org.pithyx.logs |
Log Center | administrators | single |
org.pithyx.viewer |
Viewer | granted users, opened with a file only | multi |
org.pithyx.about |
About Pithyx | everyone | single |
org.pithyx.store |
Store | administrators | single |
Core apps use the same @pithyx/sdk API as third-party apps, but in the desktop’s own context: CoreSdkApp connects the window to the host through an in-process pair of endpoints instead of a MessageChannel. The app gets the same PithyxApp object. Core apps may also use extra internal host methods that are not part of the public SDK. Third-party apps run in an <iframe sandbox="allow-scripts allow-forms"> without allow-same-origin; their identity comes from the message port, never from message data.
To add a core app: add its id to apps/ids.ts and the daemon’s CoreApps.cs, add an entry to registry.tsx, add apps.<id> to i18n/en/desktop.json and i18n/de/desktop.json (a test enforces this), and write a window component that wraps CoreSdkApp.
Data and realtime
Section titled “Data and realtime”- REST. One client,
api = createApiClient(), from@pithyx/api-client.unwrap(await api.GET(...))throws aPithyxApiErrorwith code, params, request id and status. The query client retries failed queries up to 3 times, and anauth.session.expirederror sends the user back to the sign-in. - WebSocket. One
RealtimeConnectionper tab at/ws. Subscriptions are reference-counted and sent again after every reconnect.useTopic(topic)returns the latest snapshot (undefined until the first one arrives);realtime.listenhandles members of topic families such as one folder. - Reducers.
reducers.tshas one reducer per topic and is typed by the generated topic list, so a new daemon topic fails the build until the desktop handles it. - Reconnect. The delay is
min(30 s, 0.5 s * 2^attempt), randomized to 50 to 100 percent. Close code 4401 sends the user to the sign-in, 4403 stops, the others reconnect. See API. - Errors. Error messages from the socket go to the error dialog. Request ids are written to the console, never shown.
- Listings. Folder listings load in cursor pages over REST. Pithyx does not stream listings over the WebSocket or sort in a Web Worker.