artifact-bin · architecture field guideHEAD 7cbd4b8 · 01 Sep 2026

One product core, surrounded by replaceable service seams

Architecture

  • The app owns product APIs. Artifact persistence, ACLs, publishing, rendering, live collaboration, MCP operations, and the SPA all converge in services/app.
  • Contracts and utils unify cross-service libraries. The same Actor, SQL, Browser, Part, and Upstream interfaces work in one process or across HTTP.
  • The proxy is a policy edge. It resolves identity, applies rate-limit doors, hosts login/OAuth, sanitizes forwarding headers, then delegates semantic decisions to the app.
  • Documents are compiled products. Stored JSX is parsed, validated, enriched, server-rendered, hydrated in a sandbox, and connected to SQL/data/live-update transports.
Client / agent / unfurler
          │ HTTP
          ▼
┌──────────────────────────────┐
│ services/proxy               │
│ identity · doors · OAuth     │
│ forwarding-header ownership │
└──────────────┬───────────────┘
               │ Actor + Request
               ▼
┌──────────────────────────────┐
│ services/app                 │
│ API · ACL · storage · SPA    │
│ documents · live · MCP       │
└───────┬──────────────┬───────┘
        │ SqlService   │ BrowserService
        ▼              ▼
 services/sql     services/browser
 DuckDB queries   Chromium capture

Shared beneath every edge:
services/contracts  ← types + constants
services/utils      ← assembly + transports + signing
FIG. 01 — Runtime topology is selectable; contracts remain fixed.
WorkspaceOwnsMust not own
contractsActor, Part, route, DB, SQL and browser typesRuntime behavior or dependencies
utilsAssembly, transports, signing, env and service helpersProduct semantics
proxyIdentity, rate limits, login, OAuth, forwardingArtifacts or duplicated API routes
appProduct behavior and all user-facing surfacesChoosing local versus remote heavy services
sqlBound, capped, interruptible DuckDB executionPersistent product storage
browserURL-to-image Chromium captureArtifact authorization

Takeaway: package boundaries follow authority, not merely technical layers.

Composition and request path§ 2

2. Every request crosses one identity seam and one app boundary

services/proxy/src/parts.ts is an ordered declaration of middleware ownership. assemble() turns named parts into a Hono app and permits controlled replacement by name. The last part forwards unmatched traffic through an Upstream, either directly to app.fetch or over signed HTTP.

session
  bearer → account session → agent cookie → anonymous
    │
rateLimit
  route → named door → IP/actor bucket → 429 or continue
    │
loginRoutes / oauthRoutes
  proxy-owned identity endpoints
    │
forwardedHeaders
  discard caller identity claims; establish public origin
    │
forward
  inProcess(request, actor) OR overHttp(signed actor header)
    │
createAppServer()
  actorReceiver → generated API routes → document routing → SPA
FIG. 02 — The proxy may deny with 429; the app owns semantic outcomes.

Inside the app, server/routes.generated.ts is derived from filesystem route modules under app/**/route.ts. server/app.ts adds static assets, request-scoped context, uniform 404 behavior, canonical URL healing, the reader-versus-owner document split, and bootstrapped SPA page data.

Takeaway: trace identity bugs from proxy session resolution to actorOf(request); trace product verdicts from the generated route into app libraries.

Product core§ 3

3. The app core revolves around artifacts, wire operations, and storage

lib/artifacts.ts is the persistence and authorization center: create/read/update/delete, ownership, versions, revert, shares, dependencies, ref resolution, and dataflow preparation. Route handlers stay thin by delegating request-body semantics to lib/artifact-wire.ts and transport-neutral agent actions to lib/operations/registry.ts.

Core moduleRelationship
db.ts + schema.tsPGLite/Postgres adapter and additive boot-time DDL
artifacts.tsCalls DB/object stores; enforces artifact lifecycle and ACL
artifact-wire.tsTurns API bodies into shared artifact pipelines and wire responses
operations/registry.tsOne curated surface rendered as MCP tools, bearer HTTP, and docs
tokens.ts, viewer.ts, share-roles.tsCredential ownership, effective role, read/edit/annotate permissions
object-store/*Local or S3 bytes for images, datasets, and imported fonts
annotations.tsPinned discussion threads attached to stable markup anchors
profiles.ts, shelf.ts, urls.tsDiscovery, organization, public profiles, canonical and pretty paths
POST /api/artifacts or MCP create_artifact
  → operation / route translation
  → artifact-wire validates one content tier
  → markup: parse + validate + refs + CSS + dry-run data checks
    dataset: coerce + infer columns + store rows
    image: fetch/decode + optimise + store bytes
    viz: validate recipe + bindings
  → artifacts persists head + metadata
  → response returns id, URL, edit_id, version
FIG. 03 — Publish-time work makes the first read cheap and predictable.

Takeaway: new product semantics belong in the shared app pipeline first, then thin transports expose them.

Document system§ 4

4. Stored JSX becomes a sandboxed, live, data-aware document

The document subsystem is a compiler/runtime split. lib/jsx parses, validates, and serializes static JSX. lib/story extracts Helmet declarations, references, dataflow, titles, images, mutation/query contracts, and document assembly inputs. lib/story-ui interprets validated nodes against the component registry. lib/story-runtime hydrates the result and coordinates data, editing, annotations, slides, and live replacement.

stored source
   │
   ├─ lib/jsx: parse → AST → validate → canonical serialize
   │
   ├─ lib/story: Helmet + refs + flow + CSP + compiled Tailwind
   │
   ├─ lib/story-ui: AST interpreter → React component registry
   │
   ├─ lib/story/document: standalone HTML + SSR + data island
   │
   └─ lib/story-runtime: hydrate + mx API + live/data/edit transports

Reader URL /a/:id
   ├─ plain reader + simple markup → document served top-level
   └─ owner/editor/commenter/live-data → SPA shell + sandboxed frame
FIG. 04 — Server rendering and hydration consume the same interpreted document model.
SubtreeChange it when…
lib/jsxSyntax, allowed tags/URLs, validation, or round-tripping changes
components/kit + story-ui/registryA document-facing component or icon is added
lib/storyPublish/serve semantics, data declarations, refs, CSP, or edit composition changes
lib/story-runtimeHydration, live updates, editing, comments, slides, or window.mx changes
lib/viz + components/vizVega preparation, themes, maps, facets, or tooltip behavior changes
lib/story-surfaceTypography and viewport behavior shared by rendered documents changes

Takeaway: maintain parser, interpreter, SSR, and hydrated runtime as one contract; a document must mean the same thing at every stage.

Data, live state, and exports§ 5

5. Data and live updates move independently from document structure

Documents carry declarations and final geometry before query rows arrive. The runtime store resolves referenced datasets, binds scalar values, and calls query endpoints. lib/sql/run-dataflow.ts orders query dependencies and delegates execution through the registered SqlService. This keeps page paint independent of DuckDB latency while preserving a capture exception that waits for complete content.

DATA
<Value>/<Query> declarations → runtime store → /query
  → resolve readable datasets → runDataflow → SqlService → rows

LIVE
DB wakeup → /events emits {editId, version, by}
  → client fetches /events/frame
  → cached complete structure replaces frame
  → changed flow signature triggers fresh queries

EXPORT
/a/:id/export → ACL → exportImageResponse
  → short-lived signed capture URL → BrowserService
  → PNG/JPEG bytes; card mode gets version-busted cache URL
FIG. 05 — Structure, rows, liveness, and screenshots have separate transports but converge on one artifact head.

The service registry in lib/services.ts is the inversion point. Composition roots register local implementations; SQL__SERVICE_URL and BROWSER__SERVICE_URL select HTTP clients. Callers deep inside publishing, mutation, dataflow, and export never choose deployment topology.

Takeaway: preserve the transport-neutral service contracts when extending queries or exports; topology should remain a deployment concern.

Frontend and delivery§ 6

6. The SPA is product chrome; documents remain isolated content

web/ contains the Vite SPA router, shell, and pages. components/ contains app chrome and owner workflows; components/kit/ is deliberately the separate vocabulary rendered inside documents. Page-data endpoints under /api/page/* also feed server-inlined bootstrap JSON, so the first SPA paint has final geometry.

SurfacePrimary owners
Human app pagesweb/App.tsx, web/Shell.tsx, web/pages/*, components/*
Agent API and MCPapp/api/artifacts/*, app/mcp/route.ts, operations/registry.ts
Agent documentationskills/artifact-bin/*, lib/skills/*, app/docs/*
Shared documentapp/a/[id]/raw, story compiler/runtime, per-document CSP
Owner/editor viewSPA artifact page, frame, edit/comment/live transports
Static/runtime assetspublic/, generated story runtime, themes, fonts, geo assets

Takeaway: app UI and document UI may share React, but they have different security, styling, and lifecycle boundaries.

Change plan§ 7

7. Start changes at the narrowest source of truth

  1. 1. Name the authority. Decide whether the change belongs to identity/doors, artifact semantics, document meaning, UI chrome, SQL execution, or capture.
  2. 2. Change the shared seam first. Extend a contract only when behavior crosses a process boundary; otherwise keep the change inside the owning package.
  3. 3. Preserve transport parity. Agent actions should flow through the operation registry and shared artifact pipelines so HTTP, MCP, and docs stay aligned.
  4. 4. Preserve render parity. Document features must agree across validation, serialization, SSR, hydration, live frames, and export capture.
  5. 5. Test at the nearest level, then cross the seam. Use API/node/UI Vitest projects for local contracts and browser gates for behavior only a real server, stream, CSP, or browser can expose.
If changing…Begin with…Verify across…
Auth or quotasproxy/parts, contracts doors, viewer rolesproxy → actor → app ACL
Artifact CRUDartifact-wire + artifactsHTTP + MCP + versions
Document syntax/componentjsx + registrypublish → SSR → hydrate → edit
Data/chart behaviorstory/dataflow, sql, vizquery → runtime → capture
Live collaborationevents routes + frame + runtime storeACL → SSE ping → frame adoption
Deployment topologycontracts/utils + composition rootslocal and HTTP conformance

Takeaway: the shortest safe path is source of truth → shared seam → thin adapters → cross-boundary verification.