Files
pi-gui/specs/TAURI_CLIENT_ARCHITECTURE.md
alex b7fea83ed6 feat(bridge): establish local pi status bridge
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
2026-07-27 14:51:45 +02:00

141 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tauri Pi Client Architecture
## Purpose
Replace the Noctalia panel with a testable, status-bar-agnostic desktop Pi
client. Noctalia becomes an optional status indicator and launcher only.
## Decision
Use a **Tauri v2 desktop application** with a web frontend and a small Rust
host. The bridge remains the only owner of Pi processes, sessions, recovery,
and the owner-only Unix socket.
Tauri is appropriate because its frontend talks to a native host through typed
asynchronous IPC, and its WebDriver support gives the client a real E2E test
surface. Reference: <https://v2.tauri.app/concept/architecture/> and
<https://v2.tauri.app/develop/tests/webdriver/>.
## Boundaries
```text
Pi Status Bridge systemd service
└─ Unix JSONL socket: state, events, commands
└─ Tauri Rust host
├─ validates/serializes bridge requests
├─ owns subscribe connections and emits UI events
└─ exposes typed Tauri commands
└─ React frontend: desktop UI
Optional Noctalia compatibility shim
├─ reads compact state through existing client/bridge protocol
└─ launches or focuses `pi-status-ui --toggle`
```
### Security
- The frontend cannot run shell commands or open sockets.
- Only the Tauri Rust host reads the per-user Unix socket.
- The UI never receives or stores bridge lock tokens.
- No TCP listener is introduced.
- Pi remains authoritative for model, thinking, queues, tool approvals, and
extension request IDs.
## Tauri host API
The host maps the existing JSONL protocol into typed commands. It must reject
unknown operations and preserve agent scoping.
| Command | Bridge operation | Result |
| --- | --- | --- |
| `listAgents` | `list_agents` | current agent summaries |
| `selectWorktree` | `select_agent` | explicitly selected agent |
| `loadAgent` | state, transcript, commands, models | one snapshot |
| `submitPrompt` | `submit_prompt` | Pi-owned prompt/follow-up queueing |
| `abort`, `retry`, `restart` | same | command response |
| `setModel`, `setThinkingLevel` | same | command response |
| `respondToExtension` | `extension_response` | command response |
| `subscribeAgent` | `subscribe` | emits `bridge-event` to this window |
The Rust host owns reconnection and cursor replay. Its view-model event stream
is normalized to:
```ts
type BridgeEvent = {
agentId: string;
seq: number;
kind: "state" | "message" | "tool" | "queue" | "extension" | "recovery";
payload: unknown;
};
```
## Frontend
### Window behavior
- One persistent window; close hides it, quit is explicit.
- `--show`, `--hide`, and `--toggle` are routed to the existing instance using
Tauris single-instance capability.
- The optional Noctalia widget runs `pi-status-ui --toggle`; it never renders
the primary UI or handles bridge commands.
### Screens
1. **Conversation** (default): selected agent, streaming/queue/tool status,
transcript, extension dialog, and one composer.
2. **Agents**: Home plus remembered explicit folders. Selecting a remembered
folder invokes bridge `select_agent`; no folder starts merely because it is
remembered.
3. **Session settings**: model, thinking level, session metadata, retry, and
restart.
### Composer invariant
The frontend has one normal submit action. It invokes `submit_prompt`; it does
not decide between prompt, steering, or follow-up. Slash-command selection
inserts text only.
### Extension invariant
Render Pi `select`, `confirm`, `input`, and `editor` requests. Present any
other method as unsupported without sending an approval.
## Persistence
| Data | Owner |
| --- | --- |
| Pi session/recovery | bridge |
| remembered worktree paths + last selected path | UI app data |
| bridge agent IDs | memory only; invalid after bridge restart |
| compact launcher state | Noctalia only, disposable |
At startup the UI resolves its stored path through `select_agent`; it never
persists or trusts a bridge agent ID.
## Implementation sequence
1. Add `ui/` (React/Vite) and `src-tauri/`; leave current Node bridge intact.
2. Implement Rust JSONL client plus typed command/snapshot contract with unit
tests against a temporary Unix socket server.
3. Implement the conversation store and pure reducer tests for event replay.
4. Build the conversation screen; add WebDriver E2E for initial load,
composer submission, and busy follow-up behavior.
5. Build agent/settings/extension screens and tests.
6. Replace Noctalia panel entry with a small launcher compatibility shim; retain
its compact state indicator.
7. Remove the primary Luau panel only after client E2E and manual desktop checks
pass.
## Acceptance checks
- Launching `pi-status-ui --toggle` opens/focuses one window.
- Restarting the bridge recovers to Home or a saved worktree path; it never
issues a command to a stale agent ID.
- A normal submit, busy submit, model change, and extension response route
through typed host commands only.
- A custom extension UI cannot be approved from the client.
- Noctalia can be disabled/uninstalled while the desktop client remains fully
usable.
- Rust host tests, frontend unit tests, WebDriver E2E, bridge tests, and a
manual desktop run pass.