Skip to content

packages web app

Zachary BENSALEM edited this page Aug 15, 2026 · 1 revision

Web app

Active contributors: Zachary BENSALEM

Purpose

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.

Directory layout

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

Key abstractions

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.

How it works

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.

Turn and streaming state machine

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.

Steering and follow-up during a turn

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.

Out-of-turn pushes and the SSE cursor

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
Loading

Plan decisions

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.

Settings dialog flow

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.

Remote runtime mode

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.

Integration points

  • 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/design components 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.

Entry points for modification

  • 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 matching web/server handler; 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 in web/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.ts and web/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) and web/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.ts and web/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 in web/app/playwright/smoke.spec.ts.

Key source files

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/*.

Clone this wiki locally