Maia client (@maiaos/client)
The shared Expo application for Maia OS: one React Native codebase that renders Maia Node state for the Tauri desktop app today and native iOS and Android builds later, so people can follow and steer their AI coding agents by text or voice.
Status: source candidate, not release-qualified. Desktop comes first; mobile follows from the same features. The Expo web export is local Tauri content and a headless test artifact, not a standalone web application.
The client is a projection: React components render, and Effect services in
@maiaos/client-core reach Maia Node
through @maiaos/sdk and explicit platform
transports. It never owns Node authority, runtime supervision, provider APIs,
credentials or durable state.
Run it
From the repository root:
pnpm --filter @maiaos/client dev # Expo web development server
pnpm dev:desktop # the same client inside the Desktop shell
pnpm test:e2e:client # rebuild the export and run Playwright
A running Node is needed for live data; see the quick start.
Key concepts
- Truthful states. Unavailable platforms, offline Nodes, missing Gateway configuration and denied microphones show explicit, distinct states; nothing falls back to sample data or a fake send.
- Preview, then confirm. A managed text request targets an exact idle
Session, shows the Node's preview and runs only after explicit confirmation.
Uncertain outcomes stay visible until reconciled through
receipt.get. - Scope from the Node. The active Workspace scope, voice prompt and voice tool come from Maia Node; replacing the scope clears the visible voice projection.
- Voice is optional. Text is the default. Voice uses GPT Realtime through Vercel AI Gateway with a short-lived token minted by the Desktop process; the microphone starts off.
- Platform edges. SDK and Tauri imports live only in
src/platform/desktop; native mobile adapters fail closed until their capabilities are qualified. Long-lived credentials never enter the renderer.
Status and direction
Mounted today (decision): a boot gate that waits for the Desktop host to report a ready Node (starting it once, or offering "Start Maia"), then file-based routes in one app shell:
| Route | Page |
|---|---|
/ | Home: Sessions grouped by attention with statistics; Get started when empty |
/w/[workspaceId] | A Workspace's Projects with their Session counts |
/p/[projectId] | A Project's Sessions grouped by attention; ?state= pages one group |
/s/[sessionId] | One Session: its detail, composer and recorded questions |
/attention | Sessions that need a person, most urgent first |
/sessions | Every Session, with search, a state filter and pages |
/nodes | The Nodes this client reaches and their health |
/start | Get started: Node, accounts, existing sessions, first Session |
/settings, /voice | This Node, keyboard and voice (?section=); the voice conversation |
Search, filter and page live in the URL (?q=&state=&page=), so back,
forward (mod+[, mod+]), the header back arrow and deep links replay a
place. One Work cockpit above the routes (decision)
serves every page: a page holds the rows that fit the window, mod+K opens
the launcher, j/k/Enter/Escape triage, [/] turn pages, the command
dock opens New Session with a typed task, and ? lists every shortcut.
Any key hides a tooltip under the pointer or on the focused icon
(src/platform/hover-escape); an Escape that hides one does nothing else,
so the next Escape reaches the shortcuts.
Every registered remote Node that is ready gets its own cockpit; pages show
all Nodes combined or the one chosen in /nodes, and each Project or Session
action goes to the one Node that holds it. In the combined view an ID that no
Node holds, or that two Nodes' indexes hold, gets no action and says so;
choosing one Node in /nodes acts on that Node, whatever its bounded list
shows. The Session page streams the conversation, answers approval requests
through the native sheet and puts its composer in the shell's dock. Both docks offer on-device dictation on Desktop
(macOS): a microphone, mod+shift+D and Escape. The live-terminal client is
retained in src/features/terminal but not mounted.
A retry after an output fence re-delivers from cursor 0 onto the same
screen, because the renderer has no reset. The first product caller must add
one: a reset path on TerminalRendererProps that calls xterm reset() and
clears the output queue.
Desktop also lists Sessions from this computer and the remote Nodes registered
with maia node install, with a Node label and filter. New Session selects its Node
before its Project and Connection. Remote Nodes refresh every 15 seconds over
the SSH bridge; a connection failure remains visible with its reason. A creation
preview stays bound to its original Node and is disabled if that Node disappears
or its registration changes. A prepared lifecycle confirmation closes when its
Node changes. Local Sessions keep the live item stream; remote Sessions read
final turn text because the SSH bridge has no attach stream yet (D-088).
The visual language follows the owner's Pencil design: src/ui/theme
holds its light and dark tokens (neutral canvas, white content surfaces, ink,
positive, attention and negative signals, 16/24/28 px radii, 44/48/64 px
controls and three depth shadows), src/features/shell the app shell, its
three page templates and density breakpoints, and src/ui/molecules the shared header,
grouped list section, row, statistic and command dock. Icons are Lucide
geometry copied under third-party/lucide. Inter and IBM Plex Mono are named
first in the font stacks but not bundled, so a system font renders where they
are not installed.
The longer-term target in the UI specification adds one coordinator chat per Maia, Workspace or Project scope. A "Create or Pair Node" journey and a switcher across personal and organization Nodes are planned, not implemented; each SDK connection will address one explicit existing Node.
Development
pnpm --filter @maiaos/client lint
pnpm --filter @maiaos/client typecheck
pnpm --filter @maiaos/client test
pnpm --filter @maiaos/client build # exports to apps/client/dist
pnpm test:e2e:client
apps/client/dist is generated output: never commit or deploy it, and never
treat it as evidence of a connected workflow. Native mobile work uses an Expo
development build; Expo Go cannot qualify audio, deep links, secure storage or
packaging.
Documentation
- Desktop and mobile client architecture
- UI specification
- Presentation feature
- Desktop shell
- Repository map
License
No license has been published yet. All rights reserved.
In this section
- Platform
The pages of the Maia OS documentation under Platform.
Imported from Maia OS docs at a2175bb