docs(work): add Veect ADRs 027-029, glossary terms, first two PRDs

Records the 2026-07-12 grill-session decisions repurposing this repo
for Veect v1 as a hosted SaaS: ADR-027 (hosted distribution +
control-plane/runner split), ADR-028 (iframe canvas), ADR-029
(DesignDoc v1 canonical + editor rebuild). Seeds the work pipeline
with the platform-retrofit and walking-skeleton PRDs (approved) and
adds the Veect product-domain section to the glossary. These
consciously supersede the .proto bundle's Electron/local-first
distribution decisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
2026-07-12 12:17:35 +02:00
parent ee0fec0691
commit 442b70476c
6 changed files with 415 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# ADR-027 — Hosted SaaS distribution and the control-plane/runner split
**Status:** Accepted
**Date:** 2026-07-12
## Context
This repo is being repurposed from the `template-vertical` starter into **Veect** — a design-system-native canvas that connects to a code repository, discovers its real components and tokens, lets a designer compose screens constrained to that system, and publishes real TSX as an ordinary pull request. The product specification bundle lives in `.proto/veect-product-docs/` (tech spec rev 1.2.2, design spec, UI gap spec, PRD, strategy docs).
That bundle **locked** a distribution decision on 2026-07-10: standalone desktop app (Electron, bundled Node runtime) as the default, plus a self-host Docker image, with "do not relitigate" markers. Large parts of the tech spec derive from it: the `node` PATH shim, Electron ABI policy, `safeStorage` credential storage, GitHub OAuth *device flow* (a desktop app cannot hold a client secret), per-workspace SQLite, and the "designer needs no dev environment" assumption (A5) satisfied by bundling the runtime.
On 2026-07-12 the founder superseded that decision during the architecture grill session: **Veect v1 is web-based — a hosted cloud SaaS — with a Tauri desktop shell later.** The spec itself declared the editor UI shell-agnostic and Tauri a documented alternative, so the editor carries over; but "who runs the server" changes everything downstream: auth posture, secret storage, isolation, persistence, and milestone acceptance criteria.
The central new problem: Veect executes **untrusted customer repo code by design**`pnpm install` lifecycle scripts, `next dev`/Vite servers, arbitrary config plugins. On a desktop that risk belonged to the user's own machine (mitigated by a workspace-trust prompt). Hosted, it lands on Veect's infrastructure and neighbors' tenants.
## Decision
**1. Veect v1 is a hosted cloud SaaS.** Users sign in from a browser; no local install is required for the primary path. Assumption A5 is fully restored — stronger than the Electron plan ever made it. A Tauri desktop shell is a later distribution, not v1.
**2. The architecture splits into a control plane and workspace runners, joined by a single runner protocol.**
| Part | What it is | Where it runs |
| --- | --- | --- |
| **Control plane** | Multi-tenant service: auth, tenancy, workspace/project metadata, design-doc persistence, AI proxying, runner orchestration. Template-shaped: Payload + Postgres, tRPC (ADR-013 stands), features in `packages/*`, UI in `apps/web-next` | Veect-operated infrastructure |
| **Cloud runner** | Executes a workspace's repo code: clone, worktrees, dependency installs, discovery scans, preview adapters, git checkpoint/publish. **One isolated container per workspace**: CPU/mem limits, egress allowlist (npm registry, git host), no cross-tenant network | Veect-operated infrastructure |
| **Local runner** (CLI agent) | The same runner contract implemented as a CLI the developer runs against a local checkout; the browser editor reaches it directly at `127.0.0.1` after pairing with the hosted session | User's machine |
The **runner protocol** (plain WS/JSON — deliberately *not* tRPC, because a slim CLI must implement it and the browser must speak it to a local runner) is the generalization of tech spec §9's "common runtime protocol". One protocol, N runner implementations — cloud, local, and later Tauri-embedded.
**3. v1 connection modes: remote repos (cloud runner) + local checkouts (local runner).** The self-host Docker image is **cut from v1** (fast-follow for enterprise); the core stays 12-factor/deployable-anywhere so it is not foreclosed.
**4. Identity is provider-agnostic.** Email/password accounts via the existing `auth` feature; repo access is a per-workspace credential (PAT or OAuth). The device-flow screens are retired. A GitHub App is a compatible later upgrade.
**5. Control-plane persistence is Payload + Postgres.** Users, workspaces, projects, designs, revisions, and AI runs are Payload collections behind the template's repository pattern. The spec's per-workspace SQLite (§11) is **deleted from v1**: runners hold only ephemeral working state and are rebuildable from the control plane + git. Canonical design state = Postgres (working) + `design.veect.json` on the branch (shared, reviewed) with file-wins reconciliation.
**6. The telemetry and compliance stances flip with the distribution.** "No telemetry v1" was a desktop-privacy posture; operating a multi-tenant service reverses it — the OTel/Sentry stack (ADR-014/017), `core-audit`, `core-consent`, `core-dsr`, and `core-analytics` are **kept and wired** (an EU-entity SaaS with user accounts has GDPR obligations on day one, and the activation/NSM metrics require product analytics). Demo content features (`blog`, `media`, `navigation`, `marketing-pages`) and `apps/web-tanstack` are deleted; `apps/cms` stays as the Payload admin; the landing page becomes a static route in `web-next`.
**7. AI compose is Veect-metered with a BYO escape hatch.** Server-side Anthropic key with per-tenant quotas (Haiku 4.5 default, Sonnet opt-up) *and* an optional per-workspace BYO key override, both from day one. The §14 "what was sent" disclosure is a hard requirement since prompts transit Veect infrastructure.
## Alternatives considered
- **CLI + Docker local-first (`npx veect` + self-host image).** The closest web-based reading of the original spec: repos, secrets, and execution stay on the user's machine. Rejected: reintroduces the install/dev-environment friction for designers that the founder explicitly wants gone; the developer-champion has to run/maintain a server for the team.
- **Docker-only self-host.** Simplest release engineering, strongest data-residency story. Rejected: heaviest first-run, clumsy local-folder support, no zero-install designer path.
- **Electron per spec.** Rejected by the founder's web-first direction; Tauri (not Electron) is the eventual desktop shell, and the runner protocol keeps that path open.
- **Shared-host execution with OS-level separation (cloud side).** Cheapest isolation. Rejected: indefensible to the engineering/security veto persona for untrusted-code execution; container-per-workspace is the industry floor (Codespaces/StackBlitz class), hardening to gVisor/microVMs later without protocol changes.
## Consequences
- **Positive:** zero-install activation for designers (the <20 min time-to-value target gets its biggest lever); the template's machinery survives largely intact (Payload repositories, tRPC, generators, conformance, dev-seed, work pipeline); `InMemoryEventBus`/`PayloadJobsEventBus` and `IJobQueue` are all genuinely useful again; one runner codebase serves cloud, CLI, and future Tauri.
- **Negative / accepted:** repo code and env values now transit and execute on Veect infrastructure the enterprise trust question sharpens ("your code leaves your machines") and needs a security FAQ before design-partner outreach; per-workspace compute (installs, dev servers) is a real COGS line requiring hibernation/pooling; the local runner is a second runner implementation to maintain from v1; publishes author as the workspace's stored credential (multi-seat honesty must be surfaced in the publish UI).
- **Retired spec surface (do not implement):** Electron shell, bundled-runtime PATH shim, ABI policy, `safeStorage`, auto-update/adapter-drain, deep links, GitHub device flow, per-workspace SQLite, self-host Docker (v1), SSH-via-system-agent for cloud runners (returns with the local runner, where an agent exists).
- **Milestones M0M7 must be re-derived**: the M0 acceptance ("boots with no system Node") is meaningless hosted; its replacement is the walking-skeleton PRD (sign-in connect fixture repo cloud runner one real component in the editor).
- The `.proto` bundle remains the product-behavior authority **except** where this ADR and the grill decisions supersede it; the authority table in `.proto/veect-product-docs/README.md` §2 is amended by this ADR.
## Related
- ADR-013 (tRPC conventions stands), ADR-014/017 (instrumentation exporter layer now stays), ADR-015 (events/jobs both impls in use), ADR-016 (realtime control-plane pushes), ADR-025 (EU compliance baseline applies for real now)
- ADR-028 (iframe canvas), ADR-029 (DesignDoc v1 + editor rebuild)
- `.proto/veect-product-docs/veect-technical-spec.md` rev 1.2.2 (superseded on distribution, §3611 partially retired)
- Glossary: "Veect product domain" section in `docs/glossary.md`

View File

@@ -0,0 +1,41 @@
# ADR-028 — Iframe canvas: the one-runtime invariant on the board
**Status:** Accepted
**Date:** 2026-07-12
## Context
Veect's first product invariant is **one runtime**: canvas, preview, isolation, and exported code render the same node tree through the same renderer — "no approximations, no 'roughly how it'll look'". The tech spec strengthens it for repo-native mode: every component instance on the canvas is *the repo's own module compiled by the repo's own framework toolchain* (embedded Vite / managed `next dev`), rendered into the editor through a cross-origin iframe with a pinned-`targetOrigin` postMessage protocol (`runtime.ready`, `fonts.loaded`-gated measurement).
The inherited editor prototype renders frames differently: **NodeRenderer**, a hand-built React component that approximates a 13-type demo kit with inline styles. It is excellent for the Playground fiction it was built for — and it is precisely the "approximation" the invariant forbids for real repos. The product bundle simultaneously says "inherit the editor, do not rebuild" and specifies an architecture in which the canvas interior cannot be NodeRenderer. This collision was unowned by any milestone; it is the hardest UI-architecture problem in the product (selection, hover, drag, hit-testing, and measurement across an origin boundary; React Flow frames containing live iframes; pan/zoom performance).
## Decision
**1. The canvas renders real repo components through iframes served by the workspace runner's preview adapter.** Each frame on the React Flow board hosts a cross-origin iframe whose content is the frame's subtree rendered by the repo's own toolchain inside the runner (ADR-027). What you see on the board *is* the compiled output — the invariant holds by construction, and the checkpoint parity check compares like with like.
**2. An injected agent script provides editor affordances over postMessage.** The adapter serves the frame wrapped with a Veect agent script that implements: node hit-testing and selection reporting, hover highlights, geometry measurement (post-`fonts.loaded`), scroll/viewport sync, and drop-target resolution for drag-from-library. The editor side speaks this **canvas protocol** (part of the runner protocol family, tech spec §9) with pinned `targetOrigin` both ways. Direct manipulation renders selection/hover rings as editor-side overlays positioned from reported geometry — chrome never lives inside the customer's document.
**3. NodeRenderer retires to the Playground** (the no-repo sandbox and landing-page demo funnel), where the base kit is the registry and an in-editor renderer is honest. It may also serve as a boot skeleton while an adapter cold-starts, clearly presented as "starting preview", never as the real render.
**4. Latency budgets are honored by splitting interaction from render.** Board pan/zoom and frame dragging are React Flow transforms on the iframe containers (no round-trip). Tree mutations flow through the doc engine to the adapter via HMR-grade updates; the <16 ms/frame budget applies to direct manipulation, the <500 ms budget (spec §7) to content updates.
## Alternatives considered
- **NodeRenderer-first MVP, iframes later.** Fastest to demo and maximizes prototype inheritance. Rejected: it ships the exact lie the wedge attacks the trust bar ("an engineer would merge this because preview equals output") cannot be honestly measured against approximated rendering, and the migration rewrites the canvas anyway.
- **Hybrid per node type** (base-kit nodes via NodeRenderer, discovered components via per-component iframe islands, Storybook-composition style). Rejected: two runtimes on one canvas cross-island layout, double theming, and parity semantics become incoherent; the invariant is violated at every boundary between the two.
- **Server-side screenshot streaming** (render in the runner, stream images). Rejected: kills interaction latency and real hover/focus states; contradicts the "real components with real states for free" promise.
## Consequences
- The canvas protocol (agent script + postMessage surface) becomes a named deliverable of the walking-skeleton and editor-foundation PRDs it is the technical risk to front-load, exactly as the original spec front-loaded adapter risk in M0.
- The editor rebuild (ADR-029) targets this architecture from the start; no NodeRenderer-era selection/hit-testing code is ported to the board path.
- Per-frame iframes bound the practical frame count per board; the adapter serves multiple frames from one origin with one iframe per frame (or view), and the LRU adapter-pool rules from the spec apply per workspace, not per frame.
- Multi-view frames render as N iframes of the same subtree at different widths the view-cluster math from the prototype's `engine/views.ts` survives as editor-side layout logic.
- Security: the iframe boundary is also the trust boundary customer code never executes in the editor's origin; the agent script is Veect-authored and injected by the adapter, never sourced from the repo.
## Related
- ADR-027 (control plane / runner split the adapter lives in the runner), ADR-029 (editor rebuild)
- Tech spec §1 (invariant), §9 (adapters + protocol), §16 (budgets); UI gap spec §3 (adapter status states, server-component affordances)
- Glossary: **Iframe canvas**, **Preview adapter**, **Canvas protocol** (under runner protocol), **Playground**

View File

@@ -0,0 +1,45 @@
# ADR-029 — DesignDoc v1 as the committed schema; editor rebuilt to conventions
**Status:** Accepted
**Date:** 2026-07-12
## Context
Two related inheritance questions from the `.proto` bundle needed resolution before PRDs could be written.
**The schema schism.** The bundle contains two different design-document models: the tech spec's **DesignDoc v1** (§8 — Frame/Element/Text nodes; `lit | token | expr` prop values where `expr` renders as a chip and emits verbatim with a review marker; Element nodes referencing *arbitrary discovered components*; unknown-component error states) and the prototype's **VeectNode** (13 hardcoded demo-kit types with one flat props bag). This choice is contract-level: `design.veect.json` is committed on the project's branch and reviewed by engineers in every Veect PR — the single hardest artifact to migrate later.
**The inheritance question.** The bundle loudly locks "inherit the editor, do not rebuild — the codebase seeds `packages/editor`". But analysis showed the bundle holds *three generations* of the editor (React codebase < written design spec < `Veect.dc.html` interactive prototype), with the codebase missing table-stakes surfaces (history panel, preview overlay, tool pill, K, toasts, AI ghost rendering, node deletion). The lock's premises also predate the 2026-07-12 decisions: the iframe canvas (ADR-028) retires NodeRenderer the codebase's biggest organism from the board path; DesignDoc v1 replaces the store's node model; and the hosted topology (ADR-027) moves the editor into `apps/web-next` on React 19, ending its Vite standalone-ness. What the lock protected has mostly been invalidated by newer decisions.
## Decision
**1. DesignDoc v1 (tech spec §8) is the canonical schema for `design.veect.json`.** Zod schemas live in the `design-doc` feature (entities layer). Element nodes reference discovered components by registry id; prop values are `lit | token | expr`; `expr` is never evaluated it renders as a chip and emits verbatim with a `// veect:expr` review marker. The Playground's base kit is just another registry, so Playground documents use the same schema.
**2. The editor's in-memory model is a view-model with an explicit mapping layer.** Whatever shape the editor store needs for interaction performance, it maps to/from DesignDoc v1 at the persistence and codegen boundaries. The prototype's `VeectNode` shape is at most an internal detail of the Playground never the committed contract.
**3. Code-panel edit mode ships honestly.** The bidirectional grammar is proven for the generated shape of registry components; it is *not* proven for arbitrary hand-written TSX. Edit mode is therefore enabled exactly where the grammar provably round-trips (golden tests emit parse emit byte-identical, per spec §12), and the panel downgrades to read-only with an explanatory banner elsewhere (server-component subtrees, `expr`-bearing nodes, code-owned files). Honesty over false parity the same voice as the parity-failure state.
**4. The editor UI is rebuilt under template conventions, not vendored.** This deliberately reverses the bundle's "do not rebuild" lock, for the reasons in Context. The rebuild is conformance-native from day one: components in `packages/editor` (+ the ink design language in `core-ui`) with stories and tests as the ESLint rules expect, React 19, store designed around DesignDoc v1 and the iframe canvas.
**5. What "inherit the editor" still means — the engine logic and the design.** The prototype's pure logic ports as tested libraries: the codegen/parser grammar (generalized per §12), the view-cluster math (`views.ts`), the Polish rule engine, the AI whitelist sanitizer, color/token math. The interaction design, copy, and visual language port from the design artifacts with this **ground-truth order: `Veect.dc.html` (newest — interaction + copy) > `veect-design-spec-current.md` > prototype codebase (reference only, never vendored).** This amends the bundle's authority table, which had no row for the HTML.
## Alternatives considered
- **VeectNode as the committed schema, extended with a `custom` type.** Fastest vendoring, no mapping layer. Rejected: bakes demo-kit assumptions into the reviewed contract; migrating design partners' committed JSON later is the migration you never want.
- **Vendor the codebase + "editor completion" epic to HTML parity.** The pre-grill recommendation. Rejected by the founder in light of ADR-027/028: the completion epic would rebuild the canvas, store, and React version anyway while dragging along un-storied, un-tested vendored files behind a lint-exemption zone.
- **Adopt DesignDoc v1 but cut edit mode from v1.** Safest. Rejected: the bidirectional grammar is one of the prototype's two headline innovations and the tech spec explicitly adopted it; the honest-downgrade rule contains the risk without cutting the feature.
## Consequences
- An **editor-foundation PRD** (board shell, panels, store on DesignDoc v1, keyboard model, canvas protocol client) precedes the subsystem PRDs that add panels; there is no "editor completion" epic.
- `component-must-have-story` / `component-must-have-test` apply to the editor from the first commit no exemption zone, no tightening debt.
- The grammar's generalization to arbitrary registry components (imports via registry metadata, prop serialization rules, escaping the prototype never escaped text content) is in scope for the codegen PRD, with the round-trip golden suite as its gate.
- The prototype codebase and `.dc.html` files are committed as reference material (location decided in the platform-retrofit PRD) so dispatch agents can read them; `packages/editor` starts empty.
- Node identity across code edits (the prototype minted fresh ids on parse-back, breaking anchor claims) must be solved in the design-doc PRD stable-id preservation is a schema-level concern.
## Related
- ADR-027 (hosted topology React 19 / web-next placement), ADR-028 (iframe canvas what the rebuilt board targets)
- Tech spec §8 (DesignDoc v1), §12 (codegen + golden tests); design spec §5 (prototype node model), §9 (grammar)
- Glossary: **Design doc**, **Registry**, **Iframe canvas**, **Playground**

View File

@@ -406,6 +406,60 @@ The Renovate-triggered re-walk of `evaluate-library` when a runtime dep's major
**Dev-seed mode**:
`USE_DEV_SEED=true` (or default fallback when not in production) → `bindAll()` wires populated mocks + `InMemoryEventBus` + `InMemoryJobQueue`. Developer default so `pnpm dev` boots without Payload running.
## Veect product domain
Terms for the Veect product being built in this repo (hosted design-to-code SaaS; source bundle in `.proto/veect-product-docs/`, superseded on distribution by the 2026-07-12 founder decisions). Vocabulary here wins over the `.proto` docs where they disagree.
**Control plane**:
The hosted multi-tenant Veect service — auth, tenancy, project metadata, design-doc persistence, AI proxying, and orchestration of workspace runners. The part of Veect that Veect operates.
**Workspace** (Veect domain):
A connected repository plus its Veect-side state — credentials, env values, trust settings, runner, discovered registry. One workspace = one repo. Owned by the `workspaces` feature.
_Avoid:_ pnpm workspace (build tooling sense) — qualify when ambiguous.
**Project** (Veect domain):
A design project inside a workspace — a git branch (`veect/<slug>`) backed by a managed worktree, carrying checkpoints and publish state. Owned by the `projects` feature.
_Avoid:_ "project" in the generic repo/monorepo sense.
**Workspace runner** (a.k.a. **runner**):
The process boundary that executes a connected repo's own code — clone, worktrees, dependency installs, discovery scans, preview adapters (dev servers), and git operations. Talks to the control plane over the **runner protocol**. Two implementations: **cloud runner** and **local runner**.
**Cloud runner**:
The Veect-operated runner implementation — one isolated container per workspace (CPU/mem limits, egress allowlist, no cross-tenant network) on Veect-managed infrastructure. Founder decision 2026-07-12.
**Local runner** (a.k.a. **CLI agent**):
The user-operated runner implementation — a CLI the developer runs against a local checkout; the browser editor reaches it directly (127.0.0.1) after pairing with the hosted session. Ships in v1 (founder decision 2026-07-12). The successor of the spec's "local folder" connection mode.
**Runner protocol**:
The single contract between control plane/editor and any runner implementation — the generalization of the tech spec §9 "common runtime protocol". One protocol, N runners (cloud, local, later Tauri-embedded).
**Preview adapter**:
The runner-side process that serves a workspace's frames through the repo's own toolchain (embedded Vite or managed `next dev`), one dedicated origin per adapter. The canvas's iframes and the parity check both render through it.
**Canvas agent** (and **canvas protocol**):
The Veect-authored script the preview adapter injects into every frame host page — hit-testing, selection reporting, geometry measurement (`fonts.loaded`-gated), drop-target resolution — speaking a pinned-`targetOrigin` postMessage contract (the canvas protocol, part of the runner-protocol family). Never sourced from the customer repo. See ADR-028.
**Iframe canvas**:
The canvas rendering model (founder decision 2026-07-12): React Flow frames host cross-origin iframes served by the preview adapter; selection/hover/hit-testing/measurement run through an injected agent script over postMessage. Honors the one-runtime invariant — the canvas is the repo's compiled output, never an approximation.
**Playground**:
The no-repo sandbox mode — token-paste/5-token onboarding, base kit, NodeRenderer rendering. The only place the inherited NodeRenderer remains the canvas; also the landing-page demo funnel.
**Design doc** (`design.veect.json`):
The canonical, committed, PR-reviewable design document on a project's branch — DesignDoc v1 schema (tech spec §8): Frame/Element/Text nodes, `lit | token | expr` prop values, Element nodes referencing discovered components by registry id. The editor's in-memory model maps to it; it is the contract engineers review.
_Avoid:_ the prototype's 13-type `VeectNode` shape — that is a Playground-internal view-model, never the committed schema.
**Registry** (Veect domain):
The discovered component + token inventory of a workspace (`ComponentMeta` + `TokenRegistry`), produced by the discovery feature from the repo's own source. The AI whitelist, the library panel, and codegen all consume it. "0 unregistered elements" is measured against it.
**Checkpoint** (Veect verb):
Committing a project's current state to its branch — codegen → prettier → diagnostics → parity check → atomic commit, executed as one transaction inside the runner. Distinct from autosave (control-plane persistence).
**Publish** (Veect verb):
Pushing a project's branch to origin and surfacing the compare/PR link. Never touches the base branch.
_Avoid:_ "export" for this action — export survives only as the Playground/CI artifact path (`veect export`).
_Avoid:_ confusing the Veect **workspace** (a connected repo + its state) with a pnpm workspace, and Veect **worktree** (git worktree backing a design project) with the dispatch loop's agent-isolation worktrees.
## Relationships
- A **PRD** decomposes into one or more **Epics**.

View File

@@ -0,0 +1,111 @@
---
id: platform-retrofit
title: Platform retrofit — repurpose template-vertical as the Veect monorepo
type: prd
status: approved
author: Danijel Martinek
elicitation-session: session_016j8z4VHjedXDTjEDNg7qHK
created: 2026-07-12T09:05:00Z
---
## Problem
This repo is the `template-vertical` starter: five demo content features (auth, blog, media, marketing-pages, navigation), three frontend apps, and a work tree full of shipped template epics. We are building **Veect** in it — a hosted design-to-code SaaS (ADR-027) — and almost none of the demo *content* is Veect, while almost all of the *machinery* (conformance, coverage, generators, work pipeline, DI/testing conventions, compliance and instrumentation cores) is exactly what Veect needs.
Three concrete pains today:
1. **Dispatch agents cannot read the product spec.** The entire Veect specification bundle sits untracked in `.proto/` (plus a stray `veect-product-docs.zip` at root). Sandcastle implementers run in git worktrees, which contain only committed files — every PRD referencing the spec would be unreadable to the agents implementing it.
2. **The repo actively misleads.** `pnpm work status` surfaces eight shipped template epics; CLAUDE.md/AGENTS.md describe a blog/CMS starter; release-please tracks five demo features that are about to be deleted. Agent context assembled from this repo points at the wrong product.
3. **Demo content blocks the package map.** The Veect features (workspaces, projects, design-doc, discovery, ai — per the 2026-07-12 grill decisions) can't land cleanly while blog/media/navigation/marketing-pages occupy the workspace, the router/CMS composition, and the versioning config.
## Goal
The repo is a clean, truthful Veect monorepo: only Veect-relevant packages, the product docs committed and authoritative, the work pipeline showing only Veect work — with every conformance gate still green.
## In scope
- Delete demo features `blog`, `media`, `navigation`, `marketing-pages` and app `web-tanstack`, including all composition wiring (core-api router, core-cms collections, web-next pages/imports, bindAll dispatcher, seeds, e2e specs, Storybook references).
- Reduce `apps/web-next` to a compiling shell: auth (sign-in/out) + a placeholder home. `apps/cms` stays (Payload admin over the remaining collections). `apps/storybook` stays.
- Keep and leave wired: `auth` feature, `core-audit`, `core-analytics`, `core-consent`, `core-dsr`, OTel/Sentry instrumentation (ADR-027 §6 flips the spec's "no telemetry" stance).
- Scaffold `core-events` and `core-realtime` via `pnpm turbo gen core-package` (needed by the walking skeleton for runner status events/pushes).
- Migrate `.proto/veect-product-docs/``docs/product/` (committed); migrate the design references (`Veect.dc.html`, `Veect Landing.dc.html`, `Weave backup`, `veect-codebase/` prototype, upload PNGs) → `docs/product/reference/` (committed, read-only reference — the prototype codebase is **never** vendored into `packages/`, per ADR-029). Delete `veect-product-docs.zip` and the emptied `.proto/`.
- Amend the committed product README's authority table with the ADR-027/028/029 supersessions and the `Veect.dc.html` ground-truth row (ADR-029 §5).
- Archive the eight shipped template epics and their nine PRDs to `docs/work/archive/`; rebuild `_state.json` so `pnpm work status` shows only Veect work.
- Rewrite `release-please-config.json` + manifest to track the **root only** (single product version; hosted SaaS deploys continuously — per-feature versions return only if an npm distribution ships).
- Rewrite CLAUDE.md and AGENTS.md project-overview sections for Veect (control-plane/runner vocabulary, new package map, ADR-027/028/029 references); fix the "15 conformance rules" → sixteen miscount.
- Library-trace batch (`/evaluate-library`, ADR-022) for the feature-tier runtime deps the next PRD needs: `@xyflow/react`, `zustand` (v5). (Runner-side deps like execa/vite/react-docgen-typescript live in the app-tier `apps/runner` and are exempt from ADR-022; traces for them are optional hardening.)
- Verify the ESLint/turbo boundary configs and generators survive the deletions (generators are template-driven, not dependent on the deleted features).
## Out of scope
- Any new Veect feature code (walking-skeleton PRD and successors).
- The landing page (arrives with the `polish-playground-landing` PRD; this PRD deletes `marketing-pages` and leaves no marketing surface).
- Generator retargeting beyond what deletions force — the feature generator's Payload+tRPC shape is **correct** for Veect's control plane (ADR-027 §5) and stays as-is.
- Runner infrastructure, Docker images, fixtures (walking-skeleton PRD).
- Billing, GitHub App, self-host distribution (all deferred per ADR-027).
## Constraints
- ADR-027 (hosted SaaS + runner split), ADR-028 (iframe canvas), ADR-029 (DesignDoc v1 + editor rebuild) are the decision baseline; this PRD implements their repo-level consequences only.
- ADR-021 (release-please) — the versioning rewrite must keep Conventional-Commit → release automation working; ADR-022 (library traces) — pre-commit hook must pass on every slice.
- Conformance rules stay enforced throughout: every deletion slice leaves `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit` green — no "big bang broken middle".
- The `auth` feature's manifest, tests, and coverage bands must remain intact (it is load-bearing for Veect accounts, ADR-027 §4).
- Deletions must not orphan `docs/glossary.md` terms — the template terms describing deleted features get pruned in the same slice that deletes the feature.
## Success criteria
- `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow && pnpm coverage:diff` all green on the final commit.
- `pnpm dev` boots web-next (dev-seed mode, no Payload required) with sign-in working and no imports from deleted features anywhere (`pnpm turbo boundaries` clean).
- `git ls-files docs/product/` shows the full committed spec bundle; `.proto/` and `veect-product-docs.zip` are gone.
- `pnpm work status` lists zero template epics; archived work remains greppable under `docs/work/archive/`.
- `release-please-config.json` validates and tracks only the root package.
- `docs/library-decisions/` contains approved traces for `@xyflow/react` and `zustand`.
- CLAUDE.md/AGENTS.md describe Veect (spot check: no mention of blog/media/marketing-pages as live features).
## User stories
1. As an AI implementer dispatched into a worktree, I want the product spec committed under `docs/product/`, so that I can read the authoritative requirements without host-machine access.
2. As the founder, I want `pnpm work status` to show only Veect work, so that dispatch context and prioritization aren't polluted by shipped template epics.
3. As an AI decomposer, I want CLAUDE.md/AGENTS.md/glossary to describe the Veect architecture, so that generated epics use the right vocabulary and package map.
4. As a developer, I want the repo to typecheck/test green with only auth + core packages, so that Veect features land on a stable floor.
5. As a release engineer, I want release-please to track a single product version, so that merges to main produce one meaningful release PR instead of five demo-feature bumps.
6. As an AI implementer, I want library traces for the editor's runtime deps pre-approved, so that walking-skeleton tasks don't stall on the library-policy pre-commit hook.
7. As a future contributor, I want the prototype codebase and HTML prototypes committed as reference material with a stated authority order, so that design questions have a canonical source (ADR-029 §5).
## Implementation decisions
- **Deletion order is dependency-order:** apps first (`web-tanstack`), then features (`blog`, `media`, `marketing-pages`, `navigation`), pruning each feature's wiring (core-api router anchor, core-cms collection anchor, bindAll entry, web-next UI imports, seeds, e2e) in the same slice as the package deletion. Each deletion is one slice/commit.
- **`web-next` shell:** keep the auth UI surfaces and app bootstrap (`bindAll()`, instrumentation); replace the home page with a minimal authenticated placeholder (the workspaces UI arrives in the next PRD).
- **`docs/product/` layout:** spec docs at top level (filenames preserved from the bundle); `reference/` subfolder for HTML prototypes + prototype codebase + PNGs. The prototype codebase's `package.json` stays outside pnpm workspace globs (`packages/*`, `apps/*`) by construction; add it to fallow/lint ignore lists so whole-codebase auditors skip reference material.
- **Authority amendment:** a short "Repo supersessions" section prepended to `docs/product/README.md` linking ADR-027/028/029 and adding the `Veect.dc.html` row to the authority table.
- **Work archive:** `docs/work/archive/{epics,prds}/` with a one-line README; `pnpm work rebuild-state` regenerates `_state.json`. Verify the work CLI ignores the archive path (adjust its glob if not).
- **release-please:** root-only config; `include-component-in-tag` dropped (single `v*` tags); pre-1.0 bump policy retained.
- **Optional cores:** `core-events` + `core-realtime` scaffolded by generator with their pre-shipped traces (ADR-022 "pre-shipped trace" mechanism); no consumers wired yet beyond compile-green.
- **Glossary:** Veect domain section already exists (added 2026-07-12); prune `blog`/`media`/`marketing-pages`/`navigation` mentions from the Packages section as those features are deleted; `auth` stays.
- No Payload schema changes; no new collections (those come with `workspaces` in the next PRD).
## Testing decisions
- This PRD is mostly subtractive: the test suite itself is the safety net — every slice must keep the full gate set green, and deleting a feature deletes its tests in the same slice.
- `auth` keeps its full test suite + coverage bands untouched (regression canary for the surviving floor).
- Add one boot smoke assertion (dev-seed): `bindAll()` resolves with only auth bound — proves no dangling DI symbols from deleted features (mirror the existing boot-assertion pattern in web-next's server bootstrap tests).
- Scaffolded `core-events`/`core-realtime` ship with their generator-emitted test suites; no additional coverage required until consumers exist.
- e2e: prune deleted-feature Playwright specs; keep an auth sign-in spec green as the surviving e2e baseline.
## Open questions
- Q1: Do the HTML prototypes (~640 KB total) go into git as-is or via LFS? — **As-is**; they're text, they diff, and the repo has no LFS setup to maintain.
- Q2: Should `navigation` survive temporarily as a generator reference? — **No**; the generators are template-driven (`turbo/generators/templates/`), not derived from live features. Delete all four content features.
- Q3: Keep `sentry-pii-guard.yml` CI? — **Yes**; ADR-027 keeps the Sentry/OTel stack, so the PII guard remains meaningful.
## Out of scope (deferred)
- Retargeting generator templates toward runner-side patterns (revisit after the first runner feature ships — rule-of-three).
- Security FAQ / trust documentation for design partners (needed before outreach, not before code — founder-owned, tracked in `docs/product/founder-os-audit.md`).
- npm `veect` name registration and related legal items (founder-owned).
## Further notes
- Builds on: ADR-027, ADR-028, ADR-029; the 2026-07-12 grill session decision log (13 decisions).
- Blocks: `walking-skeleton` (next PRD) — that PRD assumes the clean floor this one produces.
- The `.proto` bundle's own README §3 lists what the prototype seeds; ADR-029 supersedes its "codebase seeds packages/editor" instruction — reference-only now.

View File

@@ -0,0 +1,103 @@
---
id: walking-skeleton
title: Walking skeleton — one real component, end to end through the hosted spine
type: prd
status: approved
author: Danijel Martinek
elicitation-session: session_016j8z4VHjedXDTjEDNg7qHK
created: 2026-07-12T09:10:00Z
---
## Problem
Every Veect risk that matters lives in the seams, not the parts: control plane ↔ runner provisioning, runner ↔ repo toolchain (clone/install/dev-server of code we don't control), and editor ↔ iframe canvas across an origin boundary (ADR-028). The original tech spec front-loaded exactly this with its M0 "walking skeleton" — but its acceptance criteria ("boots with no system Node") died with the Electron distribution (ADR-027). Nothing yet proves the hosted spine works at all, and every subsequent PRD (workspaces, discovery, design-doc, codegen, projects, ai) builds on these seams. If the spine is wrong, we must find out before nine PRDs are stacked on it.
## Goal
A signed-in user connects a fixture repo; a cloud runner clones and installs it; discovery finds one real component; the editor renders that component on the board **through the repo's own Vite** in an iframe; clicking it inside the iframe selects it in the editor. One thin, honest, end-to-end vertical.
## In scope
- **`workspaces` feature (new, minimal):** `connectWorkspace` (name + git URL + PAT), `getWorkspaceStatus`, `listWorkspaces`; Workspaces Payload collection with encrypted credential storage; runner provisioning + lifecycle status events.
- **`discovery` feature (new, minimal):** registry snapshot storage; `getRegistry`; scan-result ingestion from runner events. Skeleton bar: one component with name + props from the fixture (full `ComponentMeta` fidelity comes in `discovery-and-library`).
- **`core-runner-protocol` (new core package):** versioned, zod-validated message schemas for the runner protocol (hello/ready, clone, install, scan, adapter-start, render-frame, status/error events) shared by features, editor, and runner.
- **`apps/runner` (new app):** the runner process — WS server speaking the protocol; git clone (PAT via ephemeral credential helper); package-manager install; minimal react-docgen-typescript scan; embedded-Vite preview adapter serving a frame host page with the injected **canvas agent script**; status/progress events.
- **`packages/editor` (new, minimal):** React Flow board shell, one iframe frame node, canvas-protocol client (selection round-trip), minimal zustand store. First code of the ADR-029 rebuild — stories + tests from day one.
- **`apps/web-next`:** workspace list + connect form + staged progress (cloning → installing → scanning → starting preview, the ui-gap §5 pattern); editor route hosting the board; tRPC routers for the two features; realtime push of runner status.
- **`fixtures/vite-kitchen` (new):** minimal Vite + React + TS + Tailwind repo with a `Button` component; served to tests via local bare-repo/`git daemon` helpers in `core-testing`.
- **Runner provisioning port:** `IRunnerProvisioner` with a local-child-process implementation (dev/CI) and a single-host Docker implementation (the container-per-workspace baseline of ADR-027 §2).
- Wire `core-events` (runner lifecycle events) and `core-realtime` (browser push) end to end.
## Out of scope
- Checkpoint, publish, worktrees, `projects` feature; design-doc persistence (the skeleton's frame is an ephemeral in-memory doc in the editor).
- AI compose, Polish, history, multi-view frames, isolation, code panel.
- Next.js adapter (`preview-and-nextjs-depth`); watch/HMR and perf-500 budgets (`discovery-and-library`); full connect UX states — org walls, trust prompt, env manager (`workspaces-and-connections`).
- Local runner / CLI agent (`local-runner-cli`); quotas, metering, billing.
- Real GitHub/GitLab connections beyond git-URL+PAT against fixtures; runner fleet orchestration, hibernation, egress policy hardening (single-host Docker is the v1 baseline; hardening tracked in `workspaces-and-connections`).
## Constraints
- ADR-027: container-per-workspace is the cloud baseline; runner protocol is plain WS/JSON (not tRPC); credentials never in URLs/argv/logs (spec §6 credential-helper rules apply verbatim).
- ADR-028: the component renders through the repo's own toolchain in a cross-origin iframe with pinned `targetOrigin`; **no NodeRenderer on the board path**; chrome (selection rings) renders as editor-side overlays from agent-reported geometry.
- ADR-029: editor code follows template conventions from the first commit (`component-must-have-story`/`-test`); React 19; no prototype vendoring.
- Conformance: manifest-first for every use case in both new features; generator-first (`pnpm turbo gen feature`) for `workspaces` and `discovery`; ADR-022 traces for any new feature/core-tier runtime dep (editor deps pre-approved by `platform-retrofit`).
- Budgets (adapted from spec §16): connect-to-rendered-component < 3 min on the warm fixture; board pan/zoom stays editor-side (< 16 ms/frame no protocol round-trip on drag).
- Security floor even at fixture scale: runner containers get no cross-tenant network; the agent script is Veect-authored, adapter-injected, never sourced from the repo (ADR-028 §consequences).
## Success criteria
- Full gate set green: `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff`.
- e2e (Playwright, local-process provisioner): sign in connect `vite-kitchen` via git URL + PAT staged progress completes editor shows `Button` rendered inside the adapter's iframe (assert on iframe content, not a screenshot double) click the button in the iframe editor selection overlay appears and the selection state names the component. This is the M0-recast acceptance.
- The same flow works with the Docker provisioner on a dev machine (documented manual check; CI uses the process provisioner).
- Protocol integration suite passes against a real spawned runner + local git fixture: clone install scan returns 1 component adapter serves the frame host with the agent script.
- Credential never appears in any log, process argv, or `.git/config` in the runner workspace (asserted by the integration suite, per spec §15).
- Both feature manifests declare their use cases, events, and realtime channels; boot assertion green in dev-seed and production modes.
## User stories
1. As a signed-in user, I want to connect a repository by git URL + token, so that Veect can turn it into a design surface.
2. As a user, I want staged, honest progress while my repo clones and installs, so that a minutes-long operation never looks hung.
3. As a user, I want the editor to show my repo's real component rendered by my repo's own toolchain, so that what I see is exactly what ships (invariant #1).
4. As a user, I want to click a component on the canvas and see it selected, so that direct manipulation is proven across the iframe boundary.
5. As a user, I want a clear, named error state when clone or install fails (bad URL, bad token, install failure), so that I know the cause and next action never a blank board.
6. As the control plane operator, I want each workspace's repo code executing in its own isolated runner, so that a malicious repo cannot touch other tenants (ADR-027).
7. As an AI implementer, I want the runner protocol schemas in one shared package, so that features, editor, and runner cannot drift apart silently.
8. As a developer, I want the whole skeleton to run with a local-process runner and dev-seed mode, so that `pnpm dev` + fixtures need no cloud resources.
## Implementation decisions
- **Protocol:** WS + JSON messages, every message zod-parsed against `core-runner-protocol` schemas, envelope carries `protocolVersion: "0"` and workspace-scoped auth token. Progress is event-streamed (stage + elapsed), mirroring the ui-gap §5 staged-progress pattern.
- **Provisioning port:** `IRunnerProvisioner` (service port in `workspaces` infrastructure): `provision(workspaceId) → RunnerHandle`, `terminate`, `status`. Implementations: local child process (dev/test) and Docker single-host (dockerode or docker CLI app/infra-tier choice at implementation time). Fleet orchestration is explicitly a later concern behind the same port.
- **Credential handling:** PAT stored in the Workspaces collection encrypted AES-256-GCM with a key derived (scrypt) from `VEECT_SECRET` (the spec §13 Docker mechanism, now the primary); decrypted only server-side, passed to the runner over the authenticated protocol channel, delivered to git via ephemeral credential helper (spec §6 mechanics verbatim). The Payload API never returns the credential (write-only field).
- **Runner internals (app-tier, imperative code is expected here):** clone detect package manager install (progress events) scan (react-docgen-typescript over the fixture's component glob) start embedded Vite rooted at the clone with a Veect frame-host entry that imports the requested component and mounts the agent script. Dedicated port = dedicated origin per adapter (spec §9).
- **Canvas agent v0:** reports `runtime.ready`, component bounding boxes (post `fonts.loaded`), and click-target resolution; editor overlays selection ring from reported geometry. Pinned `targetOrigin` both directions.
- **Editor v0:** React Flow board with one frame node hosting the adapter iframe; zustand store holds registry + selection only (no document tree yet DesignDoc v1 arrives with `design-doc-and-editor-foundation`); the frame renders a single Element (the discovered component with default props) requested via `render-frame`.
- **Events/realtime:** runner lifecycle (`workspaces` publishes `runner-status-changed`; realtime channel pushes stage progress + adapter-ready to the browser). Registry snapshots land via `discovery` ingesting the scan event cross-feature reaction through `core-events`, per rule E0.
- **Manifests:** `workspaces` declares `connectWorkspace` (mutates, audits: workspace-connected), `getWorkspaceStatus`, `listWorkspaces`, publishes runner events, realtime channel; `discovery` declares `getRegistry`, consumes the scan event. Coverage bands: template defaults for both features (use cases are thin orchestration; the heavy imperative code lives in `apps/runner`, covered by the integration suite the honest-bands rationale from the grill session).
- **Fixtures:** `fixtures/` at repo root, outside pnpm workspace and turbo graph; `core-testing` gains helpers to serve a fixture as a bare repo over the git protocol for tests.
## Testing decisions
- **Use-case unit tests** for every declared use case (conformance-mandated), mocks injected directly.
- **Repository contract suite** for the Workspaces repository (mock + Payload impls) including the write-only credential field behavior.
- **Protocol integration suite** (the load-bearing one): spawns a real runner (process provisioner) against a `git daemon`-served `vite-kitchen`, drives cloneinstallscanadapter over real WS, asserts registry payload, adapter origin liveness, staged events, and credential-leak absence (grep argv/logs/`.git/config`). Runs in CI on ubuntu.
- **Editor:** stories + component tests for board/frame/overlay per conformance rules; canvas-protocol client unit-tested against a scripted agent double.
- **e2e:** the Playwright spec described in Success criteria, dev-seed + process provisioner, tagged as the repo's flagship spec.
- **Good test** here means: assert through the protocol and the UI, never against runner internals the runner must stay refactorable while the protocol is the contract.
## Open questions
- Q1: dockerode vs docker CLI for the Docker provisioner? **docker CLI via execa** (fewer moving parts, app-tier so no trace required); revisit with fleet work.
- Q2: Does the skeleton editor live at a real route (`/w/[workspace]`) or a dev-only page? **Real route**, minimal; it becomes the editor's permanent home and later PRDs extend it.
- Q3: Runner base image? **node:22-bookworm-slim + git**, pinned digest; doctor-style version checks come with `workspaces-and-connections`.
## Out of scope (deferred)
- Everything listed per-PRD in Out of scope above; plus runner resource limits tuning, egress allowlisting beyond Docker network defaults, and workspace hibernation tracked in `workspaces-and-connections`.
## Further notes
- Builds on: `platform-retrofit` (clean floor, traces, scaffolded cores). Blocks: every subsequent Veect PRD.
- Authority: ADR-027/028/029; tech spec §6 (credential mechanics), §9 (adapter/protocol), §15 (fixture + leak-assertion testing), §16 (budgets); ui-gap §5 (staged progress pattern).
- The spec's M0 acceptance ("no system Node", "`git status` untouched") is superseded by this PRD's Success criteria hosted equivalents: isolated runner, credential-leak assertions, honest progress.