-
Notifications
You must be signed in to change notification settings - Fork 0
packages web app
Active contributors: Zachary BENSALEM
web/app (@prime-agent/web) is the TanStack Start host for the Qredence web chat frontend. It is the browser-facing layer of the standalone web/ UI: it mounts the chat workspace, provides thin HTTP route wrappers over web/server handlers, and contains the client library that drives the chat shell state machine. It is one of the two consumers of the wire contract in web/protocol, alongside web/design.
Per the repo rules, browser code in web/app/src talks HTTP (NDJSON + SSE) only and never imports @earendil-works/*. All agent runtime access flows through web/server. The contract types and zod schemas are imported from @prime-agent/web-protocol (web/protocol), never redefined locally.
The stack is React 19 with TanStack Router and TanStack Start (file routes), React Query for server-state hooks, Tailwind v4 via the @tailwindcss/vite plugin, zod for wire validation, Vitest for unit tests, and Playwright for a Chromium e2e smoke. It has no framework-based backend of its own: routes are declarative createFileRoute shells whose server handlers call the exported handle* functions from @prime-agent/web-server.
web/app/
├── package.json # @prime-agent/web, private, workspace:*
├── tsconfig.json
├── vite.config.ts # TanStack Start + React + Tailwind + tsconfig-paths
├── vitest.config.ts # happy-dom, globals, v8 coverage
├── playwright.config.ts # single Chromium project against dev server
├── .tanstack/ # TanStack Start generated state
├── public/
├── playwright/
│ └── smoke.spec.ts # boots chat shell + workspace API smoke
└── src/
├── router.tsx # createTanStackRouter({ routeTree })
├── routeTree.gen.ts # generated route tree (do not hand-edit)
├── routes/
│ ├── __root.tsx # root layout: query client, CSS, Toaster
│ ├── index.tsx # Chat route, the whole workspace shell
│ └── api/ # thin wrappers over @prime-agent/web-server
│ ├── chat.ts # POST /api/chat (NDJSON turn stream)
│ ├── health.ts # GET /api/health
│ ├── chat/ # abort, command, commands, events, model,
│ │ # models, models/discover, new, providers,
│ │ # providers/oauth, question, resources,
│ │ # resume, session, sessions, settings
│ └── workspace/ # browse, file, root, tree
└── lib/
├── query-client.ts # shared React Query client (staleTime 60s)
├── auth-stub.ts # v1 no-op auth (binds to 127.0.0.1)
├── analytics-stub.ts # v1 no-op analytics
├── workspace-file.ts # loadWorkspaceFile -> chatClient.getWorkspaceFile
└── pi/ # the chat shell client library
| Type | Full path | One-line description |
|---|---|---|
useChatWorkspaceData |
web/app/src/lib/pi/use-chat-workspace-data.ts |
Top orchestration hook that assembles the entire workspace from the query hooks, shell state, usePiChat, slash actions, right-panel context, and header. |
usePiChat |
web/app/src/lib/pi/use-pi-chat.ts |
Core chat hook: owns messages, status, sessions, queue, sendMessage/stop/startNewSession/resumeSession, the SSE EventSource cursor, and forbidden-session recovery. |
usePiChatMessaging |
web/app/src/lib/pi/use-pi-chat-messaging.ts |
Turn send path: lazy session creation, sendMessage, enqueueDuringStream (steer vs follow-up), and handleStreamEvent that feeds the reducer. |
applyChatStreamEvent |
web/app/src/lib/pi/chat-stream-state.ts |
Pure reducer turning one ChatStreamEvent into the next transcript/queue/activity snapshot. |
chatClient |
web/app/src/lib/pi/chat-client.ts |
Typed HTTP client for every route, validating responses against the zod schemas from @prime-agent/web-protocol. |
readChatStream |
web/app/src/lib/pi/chat-fetch.ts |
NDJSON line reader with sequence tracking; fast path for delta/thinking, zod validation for structural events. |
resolveChatApiUrl |
web/app/src/lib/pi/chat-runtime-url.ts |
Rewrites chat API paths to a remote runtime when VITE_FLEET_PI_CHAT_RUNTIME_URL is set. |
ChatPanel |
web/app/src/lib/pi/chat-panel.tsx |
Thin wrapper that adapts shell props to the FleetPiAgentChat component in web/design. |
useChatShellState |
web/app/src/lib/pi/use-chat-shell-state.ts |
Shell UI state: model key, thinking level, right panel, theme, resource canvas width, persisted session metadata. |
useChatStorage |
web/app/src/lib/pi/use-chat-storage.ts |
Persists session metadata in localStorage key fleet-pi-chat-session. |
useActiveSessionLabel, useChatSuggestions
|
web/app/src/lib/pi/use-chat-view.ts |
Derives the session header label and contextual input suggestions from messages, resources, and the workspace tree. |
useLocalSlashActions |
web/app/src/lib/pi/use-local-slash-actions.ts |
Resolves /cmd input to client actions, fires bridge commands, and echoes results as local messages. |
buildSlashCommands |
web/app/src/lib/pi/slash-commands.ts |
Merges the web builtin command catalog with the API catalog and skill/prompt commands for autocomplete. |
useRightPanelContextValue |
web/app/src/lib/pi/use-right-panel-context-value.ts |
Builds the chatPanelData, workspaceTreeContext, and settingsActions context slices consumed by web/design. |
usePendingQuestionBar |
web/app/src/lib/pi/use-pending-question-bar.ts |
Surfaces the last unanswered tool-Question part as an InputBar prompt. |
useKernelHealth |
web/app/src/lib/pi/use-kernel-health.ts |
Polls GET /api/health for kernel readiness for the header chip. |
useChatQueries |
web/app/src/lib/pi/chat-queries.ts |
React Query hooks: models, model catalog, resources, commands, settings, providers, workspace tree, and their mutations. |
PlanModeState |
web/app/src/lib/pi/plan-state.ts |
Plan-mode client state machine: enabled/executing/pending-decision/todos and decision resolution. |
extractTodoItems |
web/app/src/lib/pi/plan-parser.ts |
Parses numbered plan todos and [DONE:n] completion markers out of assistant text. |
The chat shell is driven by useChatWorkspaceData, which wires the query hooks, shell state, and usePiChat together and feeds web/design components through ChatPanel, the header hook, and the right-panel context.
usePiChat (web/app/src/lib/pi/use-pi-chat.ts) holds messages, status, and sessionMetadata. status is one of ready | submitted | streaming | error. On first send with no session, usePiChatMessaging.ensureSession lazily POSTs POST /api/chat/new. sendMessage optimistically appends a user message, sets status to submitted, and calls chatClient.streamMessage, which POSTs POST /api/chat and feeds each NDJSON frame to handleStreamEvent. That calls the pure reducer applyChatStreamEvent (web/app/src/lib/pi/chat-stream-state.ts) to reconcile the in-flight assistant bubble: start opens a placeholder bubble, delta/thinking/tool append or upsert parts, queue updates the steering/follow-up badge, plan sets the plan label, state sets the activity label, and done merges the final transcript and promotes thinking-only turns to visible text. A start frame flips status to streaming; done flips it back to ready and refreshes the session list.
Pressing Enter mid-stream steers the current turn; pressing Alt+Enter queues a follow-up. sendMessage routes to enqueueDuringStream (web/app/src/lib/pi/use-pi-chat-messaging.ts), which POSTs the extra message with streamingBehavior: "steer" | "followUp" and optimistically appends the user message. Steered messages land in the server-side queue surfaced through queue events and the shell badge; a sessions-list refresh keeps the badge current.
For every visible sessionId, usePiChat opens one EventSource to GET /api/chat/events. The NDJSON turn stream stays authoritative during an active turn (frames are skipped while status is submitted/streaming); out-of-turn pushes such as tool-Question requests, state frames, and queue events are applied to the same reducer. Sequence numbers are written to sessionStorage under pi:sse:last-event-id:<sessionId>, so a page reload resumes the SSE cursor without a server round-trip, and the server replays frames emitted while the tab was closed. An agent_settled state frame triggers a transcript resync via chatClient.loadSession.
graph TD
A[User submits composer] --> B[usePiChatMessaging.sendMessage]
B --> C{status == streaming?}
C -->|yes, Enter| D[enqueueDuringStream steer]
C -->|yes, Alt+Enter| E[enqueueDuringStream followUp]
C -->|no| F[ensureSession + POST /api/chat]
F --> G[readChatStream NDJSON lines]
G --> H[handleStreamEvent]
H --> I[applyChatStreamEvent reducer]
I --> J[setMessagesSynced / status / queue]
J --> K[ChatPanel -> FleetPiAgentChat]
L[EventSource GET /api/chat/events] -->|tool-Question / state / queue| I
L -->|lastEventId in sessionStorage| L
usePiChat renders plan cards through enhancePlanDecisionMessages (web/app/src/lib/pi/use-pi-chat-plan-decisions.ts), which attaches onExecute/onStay/onRefine handlers to pending tool-PlanWrite parts. Answering one calls submitQuestionAnswer, POSTs POST /api/chat/question, and resolvePlanDecision (web/app/src/lib/pi/plan-state.ts) turns the selection into a planAction (execute/refine) plus a follow-up message that sendMessage sends back into the turn. plan-parser.ts keeps the todo list and completion markers in sync with assistant text.
The settings dialog is opened from the header or the command palette. useChatWorkspaceData owns settingsDialogOpen and settingsInitialTab. useRightPanelContextValue builds the settingsActions slice, which bundles the settings query, the update mutation, the provider catalog/mutations, and the model catalog for the picker. saveSettings calls useUpdateChatSettings (web/app/src/lib/pi/chat-queries.ts), which PATCHes PATCH /api/chat/settings, updates the cached settings, and invalidates the model, resource, and command queries so the shell reflects the change. The model key is recomputed from the response via resolveSavedModelKey in web/app/src/lib/pi/use-chat-workspace-data.ts.
resolveChatApiUrl (web/app/src/lib/pi/chat-runtime-url.ts) returns a rewrite when VITE_FLEET_PI_CHAT_RUNTIME_URL is set. Only chat runtime paths (health, chat, abort, question, resume, new, sessions, session, runs, run) are redirected; workspace, settings, providers, and models routes stay local. This lets the browser point at a remote web/server runtime while the host still serves assets.
- Route handlers: every
web/app/src/routes/api/**file delegates to@prime-agent/web-server(see web-server); the API surface is enumerated in web-api. - Wire contract: types and schemas come from
@prime-agent/web-protocol(see web-protocol); the client validates responses through the zod schemas. - Rendering:
web/designcomponents render the messages, tool cards, question cards, and the right panel (see web-design and tool-cards). - Streaming: streaming-chat documents the NDJSON + SSE flows end to end.
- The root app architecture: architecture.
- Add or change an HTTP endpoint: create a file under
web/app/src/routes/api/(or add a handler to an existing one) that wraps the matchingweb/serverhandler; the route id and path follow the file location. - Change turn/stream handling: edit
web/app/src/lib/pi/use-pi-chat.ts,web/app/src/lib/pi/use-pi-chat-messaging.ts, and the reducer inweb/app/src/lib/pi/chat-stream-state.ts. Keep event application pure in the reducer. - Change steering/follow-up or plan decisions: edit
web/app/src/lib/pi/use-pi-chat-messaging.tsandweb/app/src/lib/pi/plan-state.ts/web/app/src/lib/pi/plan-parser.ts. - Change slash commands: edit
web/app/src/lib/pi/slash-commands.ts(catalog +resolveLocalSlashAction) andweb/app/src/lib/pi/use-local-slash-actions.ts(actions + echo). - Change shell UI state (model picker, right panel, theme): edit
web/app/src/lib/pi/use-chat-shell-state.ts. - Change client queries/mutations: edit
web/app/src/lib/pi/chat-queries.tsandweb/app/src/lib/pi/chat-client.ts. - Change tests: vitest suites live beside the code under
web/app/src/lib/pi/*.test.ts; the Playwright smoke lives inweb/app/playwright/smoke.spec.ts.
| File | Role |
|---|---|
web/app/src/lib/pi/use-pi-chat.ts |
Chat session hook: state, session lifecycle, SSE EventSource cursor, plan/question handling, forbidden-session recovery. |
web/app/src/lib/pi/use-pi-chat-messaging.ts |
Turn send, steering, and follow-up orchestration; applies stream events through the reducer. |
web/app/src/lib/pi/chat-stream-state.ts |
Pure applyChatStreamEvent reducer plus session-metadata normalization. |
web/app/src/lib/pi/chat-client.ts |
Typed HTTP client for the whole API, with zod-validated responses. |
web/app/src/lib/pi/chat-fetch.ts |
NDJSON stream reader, ChatRequestError, metadataUrl, labelForState. |
web/app/src/lib/pi/chat-queries.ts |
React Query hooks and mutations for models, providers, resources, commands, settings, and workspace. |
web/app/src/lib/pi/use-chat-workspace-data.ts |
Top-level assembly of the workspace shell; owns settings dialog state and saveSettings. |
web/app/src/lib/pi/slash-commands.ts |
Web builtin slash-command catalog, aliases, and local action resolver. |
web/app/src/lib/pi/use-local-slash-actions.ts |
Executes local slash actions, fires bridge commands, echoes results as local messages. |
web/app/src/lib/pi/use-right-panel-context-value.ts |
Builds the right-panel and settings context slices for web/design. |
web/app/src/routes/index.tsx |
Chat route component that wires the whole workspace shell. |
web/app/src/router.tsx |
Router creation over the generated route tree. |
web/app/vite.config.ts |
Vite + TanStack Start + Tailwind + React plugin config; SSR externals for @earendil-works/*. |