Request/response cycle
1. Client — React app with TanStack Query
The frontend calls the backend exclusively through the typed ORPC client, wrapped in TanStack Query hooks. The client is initialized once inapps/web/src/infra/api/client.ts and consumed via orpc helpers in feature code.
2. Transport — HTTP to Elysia at /rpc
ORPC serializes the procedure call to an HTTP request and sends it to the Elysia server. The server (apps/server/src/index.ts) mounts the ORPC handler at /rpc. Elysia routes the request to the ORPC handler without inspecting its contents.
3. Contract — input validation
ORPC validates the incoming request against the procedure’s input schema before the handler runs. Schemas are defined using TypeBox and bridged to Zod via@sinclair/typemap:
AssistantRequestSchema, ORPC rejects the request with a typed error before any handler code runs.
4. Router — thin wiring
The router handler connects the validated input to the appropriate service. Routers contain no orchestration logic — they only delegate:5. Context — request-scoped dependencies
ThecreateContext function in packages/api/src/context.ts builds the request context that is injected into every handler. It contains the Anthropic client configuration, the filesystem adapter for slideshow persistence, and a placeholder for future session/auth data:
6. Service — orchestration
Service files (*.service.ts) coordinate domain logic, external API calls, and error handling. They are the only layer that calls both packages/core functions and external providers like Anthropic.
7. Domain — pure business logic
packages/core functions are called by services to apply business rules. Domain functions are pure TypeScript — no HTTP, no I/O, no framework dependencies.
8. Response — typed all the way back
The service returns a value matching the contract’s output schema. ORPC validates the output, serializes it, and sends it back to the client. TanStack Query receives the response and the React component re-renders with the new data — all typed.AI assistant flow
The AI assistant is the most complex data flow in the system. Here is the full path from user input to updated slideshow:Step 1 — User submits a prompt
The user types a natural language prompt in the assistant panel. The frontend packages the current slideshow state alongside the prompt into anAssistantRequest and calls orpc.slideshowAssistant.mutate().
Step 2 — Server calls Anthropic Claude
ThehandleSlideshowAssistant service in packages/api/src/slideshow/assistant-service.ts receives the request. It:
- Creates a
StateDigest— a lightweight snapshot of the slideshow state (slide count, IDs, concept keys). - Calls
buildAssistantMessagesfrompackages/coreto assemble the Anthropic message array, including the system prompt and current context (full slideshow or current slide). - Calls the Anthropic Messages API at
https://api.anthropic.com/v1/messageswithSLIDESHOW_SYSTEM_PROMPTand the assembled messages.
Step 3 — Claude returns a text response with an embedded JSON patch
Claude returns a plain text response that embeds JSON Patch operations in its body. The service extracts the patch usingextractPatchFromResponse from packages/core.
Step 4 — Patch is validated, NOT applied
The service callsvalidatePatchWithSemantics to check the patch against the current slideshow structure. This produces a PatchOrchestrationResult:
"ok"— patch is valid; aPatchTransaction(immutable record) is created viabuildPatchTransactionFromResponse"invalid"— patch failed semantic validation; errors are returned for display"noop"— no patch found in the response or the target slide is missing
Step 5 — AssistantResponse returned to client
The service returns anAssistantResponsePayload:
PatchTransaction as pending. The user clicks Apply to apply it locally. Before applying, evaluateTransactionFreshness checks whether the slideshow state has drifted since the response was generated.
AI flow diagram
The Anthropic API key is held server-side only, in validated environment variables accessed via
@slides/config/server. The frontend never has access to the key and communicates only through the typed ORPC endpoint.Load and save flow
ForslideshowLoad and slideshowSave, the flow is simpler:
- Client calls
orpc.slideshowLoad.useQuery()ororpc.slideshowSave.useMutation() - ORPC validates input against
LoadInputSchema/SaveInputSchema - Router delegates to
handleSlideshowLoad/handleSlideshowSave - Service uses the
SlideshowFsAdapterfrom context to read/write JSON files on the filesystem - Response is validated against
LoadOutputSchema/SaveOutputSchemaand returned