docs(product): commit Veect product spec bundle under docs/product/

Sandcastle implementers run in git worktrees that contain only committed
files, so every PRD referencing the spec was unreadable to dispatch
agents while the bundle sat untracked in .proto/. The eight spec
documents are copied verbatim (filenames preserved) from
.proto/veect-product-docs/ and become the canonical source.

The stray veect-product-docs.zip at the repo root was already removed
before this change — that half of the task is a verified no-op.

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:58:47 +02:00
parent 99180887f3
commit 4d1d53432e
8 changed files with 1498 additions and 0 deletions

View File

@@ -0,0 +1,310 @@
# Veect — v1 Technical Specification · rev 1.2.2
_Audience: a coding agent implementing v1. Status: implementation-ready._
_rev 1.2 (2026-07-10, founder direction): **distribution pivot** — v1 is a **standalone application** (desktop, bundled runtime) with an **additional Docker image**; repositories connect **remote-first** (GitHub / GitLab / Bitbucket / any git URL) with **local checkout** as the alternative mode; config becomes **`veect.config.json`** (machine-writable); **publishing branches to origin is now in scope**; worktrees & all Veect state move **outside the user's repo** into app-managed workspaces; the npm-installed-in-repo package (Storybook-style, rev 1.1 §1) is **deferred to a later distribution**._
_rev 1.2.1 (same day, post adversarial review of the new layers): `node` PATH shim + Electron ABI policy (the bundled-runtime promise made real) · workspace-trust model · committer identity · config carry rule · device-flow org-wall handling · SSH BatchMode/known-hosts · exact credential-helper mechanics · LFS/submodule policy · CoW worktree installs · Docker `safe.directory` + headless secret store · env manager pulled into M3 · M0 materialization contradiction resolved._
_rev 1.1 foundations retained: render-path invariant + parity check · split lazy `__veect__` registries, never committed · dedicated-origin adapters + Host validation · checkpoint transaction · docgen-at-scale rules · milestone discipline._
_Companions: `design-brief-veect.md` (UI/UX) · `design-app-prd.md` (product scope) · `company-context.md` (strategy)._
_Locked: **AI compose in v1 (Anthropic; key via app settings or env)** · **any React+TS repo, full Next.js first-class** · **SQLite locally + canonical design JSON committed in the branch** · **name: Veect / npm `veect` / scope `@veectjs/*`**._
---
## 1. What we are building
Veect is a **standalone design application that connects to a repository** — remote or local — and turns it into a design surface:
```
1. Download Veect (macOS / Windows / Linux) — no system Node required
2. Connect a repository:
· GitHub (device-flow sign-in) → pick a repo — the default path
· any git URL (HTTPS + token / SSH via agent) — GitLab, Bitbucket, self-hosted
· or a local folder — an existing checkout
3. Veect clones (remote) or attaches (local), installs dependencies with its
bundled runtime, reads veect.config.json — or walks the user through picking
the component folders visually and writes it
4. The designer composes on a canvas of the repo's REAL components;
each design project is a git worktree + branch (veect/<slug>)
5. Checkpoint → real TSX + design.veect.json committed → Publish pushes
veect/<slug> to origin → the team reviews an ordinary PR
```
A **Docker image** ships alongside: the same server, headless, for a team box, self-hosting, or CI (`docker run veect export`). The npm-in-repo package (`npm i -D veect`, Storybook-style) is a **later distribution** on the same core (§17).
**Purpose (founder's words):** bring designers closer to developers so they work on prototypes directly on the codebase, with already-defined components and all the context the codebase actually holds.
**What v1 must prove:** a designer with _no development environment_ connects a repo, and in one sitting produces an on-brand, real-code prototype on a branch that survives an engineer's review.
### The core invariant (unchanged, stated honestly)
> **Same components, same toolchain.** Every component instance on the canvas is the repo's own module compiled by the repo's own framework toolchain. Veect never re-implements or approximates a component.
Enforced, not assumed: the doc renderer styles frames with **inline styles only** (never runtime-assembled Tailwind class strings); generated TSX uses Tailwind classes (Veect registers `output.dir` in the host's Tailwind content/`@source` as part of the first published branch); **checkpoint parity check** — the generated file is rendered in the adapter and perceptually diffed against the doc-rendered canvas (threshold ~1%); parity failure blocks the checkpoint with a specific error.
### Non-goals for v1
Multi-user realtime editing · cloud sync (Docker image is self-hosted, single-team-token) · non-React frameworks · Figma import · vector tools · editing arbitrary existing app pages (Onlook's lane) · token write-back · Storybook interop · npm-in-repo distribution (deferred, not dead) · **non-git sources** · **yarn PnP** (doctor fails with message; bun best-effort).
### Prototype inheritance (rev 1.2.2 — the editor exists)
The founder's interactive prototype + codebase (`veect-design-spec-current.md`; React 18 · TS strict · Tailwind · **React Flow board substrate** · **zustand** as the in-session store) **seeds `packages/editor`** and _amends this spec_ where it went further:
- **Multi-view frames ship in v1** (supersedes "desktop breakpoint only"): frames carry `views/view` (desktop = frame `w` · tablet 768 · mobile 390), rendered as side-by-side **view clusters** of the same tree; the **active view** drives preview, code, and export width. Adapters render each enabled view's width; the doc model's `FrameNode` gains `views: View[]` + `view: ViewId`.
- **Code panel edit-mode ships** (supersedes rev 1.1 "read-only"): the codegen grammar is **bidirectional** — the generated TSX parses back with line-referenced errors. The inverse parser is part of `packages/codegen` and must stay in lockstep with the emitter (golden tests run both directions).
- **AI UX inherited:** model picker (Haiku default / Sonnet option — a per-request override of `ai.model`), image attachments in context, scoped edits (amber ring + scope chip), dashed-ghost staging, refusal protocol. The prototype's offline heuristics are retained as the **record/replay eval harness + Playground demo mode**.
- **Vocabulary:** top-bar "Export" becomes **Checkpoint** (commit) + **Publish** (push + PR link); the export-zip dialog survives only in Playground/Docker-CI contexts.
- **Integration seams:** the codebase's declared seams map 1:1 onto this spec's subsystems (`engine/ai.ts#complete` → §14 AI engine; export zip → §10 checkpoint/publish; placeholder projects → §2/§11 workspaces; auth stub → §6 connections; zustand-only persistence → §11 SQLite/JSON write-through). Wiring table: `veect-ui-gap-spec.md` §4; remaining UI surface: same doc §2§3.
---
## 2. Architecture overview
```
Veect app (desktop shell; bundled Node runtime — ELECTRON_RUN_AS_NODE for all child processes)
├── Editor UI (the web editor, in the shell's window)
├── Server core (Hono on 127.0.0.1) ──────────────── identical binary logic in the Docker image
│ ├── Connections (GitHub device flow · git URL+PAT · SSH-via-agent · local folder)
│ ├── Discovery engine (components + tokens, shared TS program, watch)
│ ├── Doc engine (model, validation, revisions)
│ ├── Codegen (doc → TSX, prettier, parity check)
│ ├── Git engine (clone/fetch/worktrees/checkpoint/publish — system git, execa)
│ └── AI engine (Anthropic, constrained to registry)
├── App data (per-OS app dir, e.g. ~/Library/Application Support/Veect)
│ └── workspaces/github.com/acme/shop/
│ ├── clone/ ← managed clone (remote mode) — partial clone,
│ │ │ .git/info/exclude hides nothing: repo stays PRISTINE
│ │ └── veect.config.json ← if committed in the repo; else workspace-local (below)
│ ├── worktrees/checkout-flow/ ← git worktree, branch veect/checkout-flow
│ │ └── <output.dir>/checkout-page/{design.veect.json, CheckoutPage.tsx}
│ ├── veect.db ← SQLite (projects, revisions, AI runs, ports)
│ ├── config.json ← workspace-local config until committed to the repo
│ └── env.enc ← env values, OS-encrypted (never in the clone/repo)
└── Preview adapters (dedicated port = dedicated origin each; common runtime protocol)
├── vite-react: embedded Vite rooted at the worktree (cwd pinned to worktree)
└── nextjs: managed `next dev` of the worktree + materialized /__veect__ route
└── Editor canvas <iframe> — cross-origin, postMessage with pinned targetOrigin
```
**Local mode:** `workspaces/<hash-of-path>/` holds `worktrees/`, `veect.db`, `env.enc`; the user's checkout is attached read-mostly — Veect writes **nothing** into it (worktrees live in app data in **both** modes; rev 1.1's `.gitignore`/shields machinery is deleted — the repo is untouched except files the user chooses to commit on Veect branches).
---
## 3. Distribution & deployment
**Standalone desktop app (default).** Electron shell 🟡: chosen because Electron embeds a real Node runtime — every child process (`next dev`, Vite, package managers, prettier) is spawned with `process.execPath` + `ELECTRON_RUN_AS_NODE=1`, so **the user needs no system Node**. (Tauri + node-sidecar is the documented alternative if binary size becomes a priority; the UI is plain web either way, so the shell is swappable.) Auto-update via standard channels — **draining/stopping all adapter child processes before relaunch** (an updated bundle orphans workers whose `execPath` vanished). **System git ≥ 2.38 IS required** in v1 (worktrees + partial clone; bundling git is out of scope) — the app detects it and walks the user through the one-time install (macOS xcode-select / Git for Windows) 🟡 flagged as the residual environment friction.
**Runtime realities (rev 1.2.1, from adversarial review — these make the bundled-runtime promise actually hold):**
- **`node` PATH shim.** npm/pnpm `.bin` wrappers and lifecycle scripts invoke a binary literally named `node` — which doesn't exist on the target machines. Veect injects a shim directory at the front of `PATH` for every child process: `node` (POSIX) and `node.exe`/`node.cmd` (Windows) exec the app binary with `ELECTRON_RUN_AS_NODE=1`. A fixture with a `node`-invoking postinstall is part of the A5 acceptance test.
- **Native-module / ABI policy.** `ELECTRON_RUN_AS_NODE` reports a Node version but Electron's ABI differs — source builds against nodejs.org headers fail at require-time. Installs run with `npm_config_runtime=electron` + matching `disturl`/target; **doctor scans host dependencies for non-N-API native modules** (better-sqlite3-class) → playbook: prebuilt-only versions or Docker mode. N-API modules (modern sharp, @next/swc) are unaffected. `engines`/`engine-strict` mismatches are surfaced, not silently ignored.
- **Vendored package managers.** Corepack ships shims, not binaries — first use would fetch over the network (proxy/offline failure). Veect vendors pinned pnpm/yarn in the bundle, pins `COREPACK_HOME` to app data, and prefetches at connect.
- **`runAsNode` fuse stays ENABLED** — the architecture requires it. This deviates from Electron's default hardening guidance and makes the signed binary a general-purpose interpreter (possible EDR flags); the accepted risk is recorded in §16. Implementers must not "fix" this.
- **Windows:** workspaces live under `%LOCALAPPDATA%` (never the roaming profile — multi-GB workspaces in Roaming is a corporate disaster); workspace dirs use short hashed names (MAX_PATH); doctor suggests a Defender exclusion for the workspaces dir.
**Docker image (additional, same release).** `veect/veect` — the server core, headless, browser UI. **Token auth mandatory** (no localhost-trust in a container): boot prints/accepts `VEECT_TOKEN`; every HTTP/WS request requires it. Single-team, single-token in v1 (multi-user auth belongs to a later cloud layer — decision log). Volumes: `/data` (workspaces). Also the **CI story**: `docker run -v $PWD:/repo veect/veect export --local /repo` runs the export gate on a checked-out `veect/*` branch.
**npm package `veect`****register the name/org now, ship later** (§17). The core is layered so the CLI/npm distribution reuses server + engines unchanged.
**Monorepo layout** (rev 1.1 §3, amended): add `packages/shell/` (Electron main, auto-update, safeStorage bridge, deep links) and `packages/connections/` (providers, credential store, clone/fetch orchestration); everything else stands.
---
## 4. Surfaces: app UX + CLI
The **GUI is primary** — connect, browse projects, design, checkpoint, publish, doctor, settings all in-app (design brief §9 incl. the new Connect Repository + Environment screens). A **`veect` CLI ships inside the app bundle** (and in the Docker image) for power users and CI:
| Command | Behavior |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `veect open [path\|url]` | Launch/focus the app on a workspace (registers as the `veect://` deep-link handler too). |
| `veect export <project> [design]` | Regenerate TSX+JSON, prettier, full `tsc --noEmit`, parity check, checkpoint commit. **CI mode:** cwd is a plain checkout of a `veect/*` branch → operates in place, no app/workspace needed. Non-zero exit on failure. |
| `veect doctor` | Environment & connection report: git version, credential reachability (never prints secrets), framework/appDir detection, Tailwind content registration, adapter health, disk usage per workspace, Windows long-path check. Pass/warn/fail + one-line fix each. |
`init` is gone as a user-facing requirement — connecting a repo **is** init. (The Storybook-style `npx veect init` returns with the npm distribution later.)
---
## 5. Configuration — `veect.config.json`
JSON, not TS (founder decision): the app must be able to **read and write** it safely from the GUI (the first-run wizard writes it; the settings screen edits it), and JSON travels through the schema. A published **JSON Schema** (`https://veect.dev/schema/config.v1.json`) gives editor autocomplete; the parser accepts JSONC (comments tolerated), the writer emits canonical JSON.
```jsonc
// veect.config.json — lives at the repo root, committed like any config
{
"$schema": "https://veect.dev/schema/config.v1.json",
"components": ["src/components/**/*.{tsx,ts}"], // REQUIRED — the design-session folders
"exclude": ["**/*.test.*", "**/*.stories.*"],
"framework": "auto", // "auto" | "nextjs" | "vite-react" | "react"
"appDir": ".", // monorepos: app package to run, e.g. "apps/web"
"preview": { "setupFile": "./veect.preview.tsx", "css": [] },
"tokens": { "tailwind": true, "cssVars": ["src/styles/tokens.css"] },
"output": {
"dir": "veect-designs",
"componentImportStyle": "alias-or-relative",
},
"git": {
"baseBranch": "auto", // "auto" = origin default
"branchPrefix": "veect/",
"autoCheckpoint": true, // commit on explicit Save/Export actions
"autoPublish": "remote-only", // push veect/<slug> after checkpoint: "remote-only" | "always" | "never"
"verifyHooks": false, // commit with --no-verify by default
},
"ai": {
"provider": "anthropic",
"model": "claude-sonnet-latest",
"maxTokensPerRequest": 8192,
},
}
```
Notes: `veect.preview.tsx` (decorators/global CSS) remains a **code** file, hand-authored — the wizard scaffolds a stub. **AI key is NOT in config** (it's a secret): sources are the app's Settings (stored via OS `safeStorage`) or `ANTHROPIC_API_KEY` env — never config, never the repo, never SQLite. **Config resolution:** committed file in the repo wins; if the repo has none, the workspace-local `config.json` (written by the wizard) applies, and the app offers to include `veect.config.json` in the project's first published branch — so the config enters the repo through an ordinary PR, and `main` is never touched by Veect directly.
---
## 6. Repository connections (new in rev 1.2)
**Modes:** `remote` (default) | `local`.
**Remote providers, v1:**
- **GitHub — OAuth Device Flow** (client_id only; no secret shippable in a desktop app): user gets a code, approves in browser, app receives a token. Requested scope: `repo` (read/write to chosen repos). Fine-grained PAT paste is the equal-class fallback.
- **Any git URL** — HTTPS with PAT/basic (GitLab, Bitbucket, Gitea, Azure DevOps, self-hosted) or **SSH via the system agent** (the app never reads or stores private keys; if no agent, instruct — a Veect-generated deploy-key flow is fast-follow).
- Native GitLab/Bitbucket OAuth: fast-follow; the URL path covers them day one.
**Credential storage:** OS keychain via Electron `safeStorage` (Docker: `VEECT_TOKEN` + provider tokens via env or the encrypted store on `/data`). Secrets never in SQLite, never in config, never logged; git operations receive credentials via an **ephemeral credential-helper** (`git -c credential.helper=...` per invocation), never embedded in remote URLs (URLs with tokens leak into `.git/config` and process lists).
**Clone strategy:** partial clone `--filter=blob:none` (fast on big repos; fully compatible with worktrees), default branch checked out; `fetch` on workspace open and on a visible "refresh" action; staleness (commits behind origin/default) surfaced per project with **Update from base** (merge, never rebase; conflicts read-only with "ask a developer" guidance — rev 1.1 rules stand).
**Access levels:** write access → full flow. **Read-only connect is allowed**: design, checkpoint locally; Publish disabled with an explanatory state ("ask for write access, or export a patch") — `git format-patch` export is the escape hatch.
**First run in an unconfigured repo:** visual wizard — browse the repo tree, multi-select component folders, pick `appDir` (monorepo), framework/tokens autodetected and shown for confirmation → writes config per §5 resolution rules.
**Dependency install:** on clone and on lockfile change, the app runs the detected package manager using the bundled runtime (npm built-in; pnpm/yarn via bundled corepack; bun = system-only best effort). Progress is first-class UI (real repos take minutes cold). **Worktrees always get their own install** (they live outside the repo dir, so ancestor `node_modules` resolution does not apply — accepted cost; pnpm's content-addressed store makes it cheap, npm/yarn are slower and show honest progress).
**Environment manager (per workspace):** real apps crash without `DATABASE_URL`. The app detects env needs from **`.env.example`, `turbo.json` `globalEnv`, t3-env schemas (`src/env.ts` — pervasive in exactly this Next.js audience), and `next.config` env keys**, presents a form, stores values OS-encrypted (`env.enc`), and **injects them into adapter AND package-manager processes** (private registries: `.npmrc` with `${NPM_TOKEN}` needs it at install time; `~/.npmrc` precedence documented) — values never touch the clone, worktrees, or git. Injected process env correctly wins over Next's on-disk `.env` loading (Next does not overwrite existing vars). Local mode additionally offers one-click import from the checkout's own gitignored `.env*` files (with a staleness note when the source file changes). Doctor's crash-sniffing playbook stays. **This ships in M3** (local mode already needs it — worktrees never contain gitignored `.env.local`).
**Connection realities (rev 1.2.1, from adversarial review):**
- **Trust model (the one new attack surface).** Cloning a repo and running its package manager + dev server = executing that repo's code inside Veect's runtime, with injected env readable. First connect shows a **workspace-trust prompt** (VS Code-style) stating exactly this; per-repo **"install without scripts"** (`--ignore-scripts`) opt-in; threat model stated in §16. Doctor surfaces `safeStorage`'s backend — on Linux without a keyring it falls back to weak protection (`basic_text`), and the UI says so rather than implying encryption.
- **GitHub org walls.** OAuth `repo` scope hits third-party-app restrictions and SAML SSO: auth "succeeds," then the org repo 404s until approved. Post-auth, Veect probes repo visibility and turns the 404 into the org-grant/SSO-authorize guidance flow — never a dead end. The **GitHub App** model (per-repo install, no OAuth wall; 8h tokens + refresh handled) is the fast-follow auth upgrade.
- **Write-permission probe at connect** (provider API or `push --dry-run` to a throwaway ref) — Publish gating is known on day one, not discovered as a 403 after a week of design work.
- **SSH from a GUI app:** `GIT_SSH_COMMAND` with `BatchMode=yes` + explicit known-hosts policy (fingerprint-confirm UI, `accept-new` thereafter — first clone must never hang on a TTY prompt). macOS GUI apps often don't inherit `SSH_AUTH_SOCK` (launchd) — doctor distinguishes "no agent reachable" from "agent has no identities" and shows the fix.
- **Credential-helper mechanics, exactly:** every git invocation gets `-c credential.helper=` (blank — suppresses OS keychain helpers that would answer or cache) followed by `-c credential.helper=<veect-helper>`; the secret reaches the helper via fd/env, never argv. **The helper rides ALL git invocations** — partial clone lazily fetches blobs during `worktree add`/checkout/log, not just clone/fetch/push. (Repo tooling running bare `git` inside a worktree can hit an unauthenticated lazy fetch — documented limitation + doctor hint.)
- **git-lfs:** design-adjacent repos are exactly the LFS population, and lfs isn't bundled with macOS git — Veect detects `.gitattributes` LFS filters, doctor warns with install guidance, and tests assert credential propagation into LFS transfers (`GIT_CONFIG_PARAMETERS` carries `-c` into filters).
- **Submodules: unsupported in v1** (doctor detects and says so plainly).
- **Install strategy (kills the tens-of-GB npm case):** the workspace clone installs once; each worktree's `node_modules` is **CoW/hardlink-cloned** from it (APFS `clonefile` / Windows ReFS / Linux reflink; fresh-install fallback on plain filesystems or lockfile drift). No redundant double-install.
---
## 7. Component & token discovery — unchanged from rev 1.1
Scan the active project's **worktree**; zero-project state scans the clone's default branch read-only. Shared TS LanguageService per worktree; `react-docgen-typescript` + propFilter stripping node_modules-inherited props; per-file failure isolation; content-hash cache; budgets **cold < 10s / warm < 2s on `perf-500`**. `ComponentMeta`/`TokenRegistry` shapes, client/server classification with self-healing, Tailwind v3/v4 token reading, watch → `registry.updated`, HMR budget **< 500ms** for worktree-local edits all stand. (_Live-sync covers the worktree; developer changes on `main` arrive via Update-from-base. Do not claim otherwise in product copy._)
## 8. Design document model — unchanged from rev 1.1
`DesignDoc v1` (one design = one folder = `design.veect.json` + generated TSX), Frame/Element/Text nodes, `lit | token | expr` prop values (`expr` renders a placeholder chip on canvas never evaluated and emits verbatim with a `// veect:expr` review marker), unknown-component loud error states, in-memory undo + durable revisions.
## 9. Preview & rendering — framework adapters (rev 1.1 rules stand, two amendments)
Dedicated-origin adapters (vite-react embedded Vite; nextjs managed `next dev` with **materialized, never-committed** `app/__veect__/**` via `.git/worktrees/<name>/info/exclude`; split lazy client/server registries with error boundaries; root-layout inheritance documented; middleware doctor check; protocol messages incl. `runtime.ready` queue-drain and `fonts.loaded`-gated measurement; LRU pool 2; crash auto-restart with backoff).
**Amendments:** (a) all adapter child processes are spawned with the **bundled runtime** (`ELECTRON_RUN_AS_NODE`), never the system Node; (b) env injection per §6 environment manager.
## 10. Git engine (rev 1.1 rules + publishing)
Execa around **system git**, array-args + `--` end-of-options, slugs `^[a-z0-9][a-z0-9-]{0,40}$`.
**Create project:** `git worktree add <appdata>/workspaces/<ws>/worktrees/<slug> -b veect/<slug> <base> --` env wiring per-worktree install materialize `__veect__` (Next mode) initial commit.
**Checkpoint transaction (unchanged):** validate codegen to temp prettier LanguageService diagnostics on generated files (<2s; full `tsc` only in `veect export`) parity check atomic move `git add <explicit paths>` `git commit --no-verify` (`veect(<slug>): <design> — <summary>` + `Veect-Design:` trailer). Any failure: nothing moves, visible dirty state, idempotent retry.
**Committer identity (rev 1.2.1):** fresh designer machines have no `user.name`/`user.email` the first commit would fail with git's "tell me who you are." Commits run with `-c user.name`/`-c user.email` sourced from the provider profile (GitHub login + noreply email) or a one-time in-app prompt; never written to the user's global gitconfig. Part of M3 acceptance on a fresh machine.
**Config carry rule (rev 1.2.1):** `veect.config.json` + the Tailwind content registration ride **every project branch as byte-identical content until Veect detects them merged into base** identical bytes make cross-branch merges resolve cleanly, and any branch's PR can be the one that lands them. (A dedicated one-time `veect/setup` PR is the documented alternative for teams that prefer it.)
**Publish failure classes (rev 1.2.1, enumerated playbooks):** protected branches / rulesets restricting branch-creation patterns (`veect/*` may be disallowed configurable `branchPrefix` is the fix) · **required signed commits** (Veect commits unsigned in v1 documented; commit signing is a fast-follow) · pre-receive commit-message policies (conventional-commits rejects `veect(<slug>):` `git.commitMessageTemplate` config is the escape hatch) · no write access (caught early by the connect-time probe; patch-export escape hatch).
**Publish (new):** `git push -u origin veect/<slug> --` after checkpoint, per `git.autoPublish` (`remote-only` default: auto in remote mode, manual button in local mode). After publish the UI surfaces the **compare/PR deep link** (`https://<host>/<owner>/<repo>/compare/<base>...veect/<slug>?expand=1` and GitLab/Bitbucket equivalents) zero provider API needed in v1; native "Create PR" via API is fast-follow.
**Safety invariants (updated, test-enforced):** all writes inside the workspace dir (path assertion on every fs/git call) · the user's checkout (local mode) is never modified no checkouts, stashes, index changes, not even `.gitignore` edits · **never push to the base/default branch** · never force-push · never delete remote branches · credentials only via ephemeral helper · branch collision reattach or new slug · archive = `git worktree remove` (+ optional `push` of final state first), branch kept · hooks bypassed by default (`verifyHooks`).
## 11. Persistence — unchanged from rev 1.1, relocated
SQLite per workspace at `<appdata>/workspaces/<ws>/veect.db` (WAL + FS probe TRUNCATE fallback; app-data dirs make network-FS problems rare). Tables as rev 1.1 (projects +`behind_base`, designs with `json_path/json_hash`, revisions ring-buffer, `ai_runs`, settings incl. adapter ports). Division of truth stands: SQLite = working state/history/provenance; `design.veect.json` in the branch = canonical/shareable; **file-wins reconciliation** with restore-revision safety net; no merging in v1.
## 12. Code generation — unchanged from rev 1.1
Deterministic TSX per design; imports via tsconfig alias-else-relative; frames Tailwind classes when detected (content registration travels with the first published branch) else typed styles; autogenerated banner + **code-owned flip** on hand-edit (never overwrite humans); "Export as page" for Next routes; quality gates split checkpoint (fast diagnostics + parity) vs export (full `tsc` + eslint).
## 13. Server & API
Hono; desktop: bound to `127.0.0.1`, the shell's window is the only client. Docker: binds `0.0.0.0` with **mandatory token auth** + Host-header validation + strict CORS/WS-origin checks (rev 1.1 DNS-rebinding rules apply everywhere). REST/WS surface as rev 1.1, plus: `POST /api/connections` (provider flows), `GET /api/workspaces`, `POST /api/projects/:slug/publish`, `GET /api/env/template` · `PUT /api/env` (values write-only the API never returns secrets), `project.staleness` + `publish.status` WS events.
**Docker realities (rev 1.2.1):** the image sets `safe.directory=*` (via `GIT_CONFIG_GLOBAL`) and documents the uid/volume contract (`--user $(id -u)` or chown guidance) otherwise git 2.35.2 "dubious ownership" breaks the flagship CI one-liner on bind mounts. Headless secret store: `safeStorage` doesn't exist without a desktop env values are encrypted **AES-GCM with a key derived (scrypt) from `VEECT_SECRET`** (falls back to the boot token with a logged warning); the secret-store abstraction lives in server core, the shell merely provides the OS backend. **Multi-seat honesty:** one token = one principal every publish authors as whoever's credentials are stored, and the env store is shared; the self-host guide says so and recommends a machine account with narrow scopes. Device flow inside a container: the code+URL are printed/displayed for approval on any browser (no in-container browser needed).
## 14. AI compose — unchanged from rev 1.1, key handling amended
Context assembly with deterministic lexical relevance + always-include set; **"what was sent" disclosure**; tool-forced JSON Node subtree; 2 repair rounds; graceful-refusal contract; ghost-layer propose/accept; provenance to `ai_runs`; CI eval via record/replay (0 unregistered elements; refusals fire). **Key sources:** app Settings (safeStorage) or env never config/repo/DB plaintext; no key documented setup-hint state.
## 15. Testing & CI (rev 1.1 + connection layer)
Fixtures unchanged (`vite-kitchen`, `next-app-kitchen` incl. middleware + redirecting root layout, `pnpm-mono`, `perf-500`, `next-pages-kitchen` M8-lite). **Added suites:** connections (device-flow mock server; PAT/SSH against a local `git daemon`/`sshd` fixture; partial-clone + worktree matrix; read-only mode; publish + collision/permission failures; credential-helper never leaks tokens into `.git/config`/argv asserted), env manager (injection, never-on-disk-in-clone), shell smoke (app boots on macOS/Windows/Linux CI runners, spawns adapter via bundled runtime with system Node **absent** from PATH the A5 test), Docker (token auth enforced; export-in-CI path). Windows runs the full git/worktree/connection suite.
## 16. Performance, security, DX budgets
| Area | Budget / rule |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| App cold start connect screen | < 3s |
| Remote connect canvas ready (vite fixture, warm npm cache) | < 3 min incl. clone + install (progress always visible; Next fixture bounded by `next dev`) |
| Canvas manipulation / server-component settle / HMR / checkpoint | rev 1.1 budgets stand (16ms · <1s · <500ms · <5s) |
| Discovery | cold <10s / warm <2s on perf-500 |
| Disk | per-workspace usage shown; archive/cleanup UI; warn > 5GB/workspace |
| Security | desktop server localhost-only; Docker token-auth mandatory; credentials via safeStorage/keychain + ephemeral git helper (all invocations); env values encrypted at rest, injected only into adapter/PM processes; **workspace-trust prompt before first install/run — executing repo code is the stated, accepted surface**; Linux `basic_text` keyring fallback surfaced to the user; **`runAsNode` fuse enabled by design (accepted LOLBin/EDR risk)**; AI payload = §14 disclosure; secrets never in config/DB/logs/URLs/argv; `__veect__` never committed; no telemetry v1 |
| Failure UX | every error carries a fix; doctor playbooks add: git missing, device-flow blocked (enterprise SSO), SSH agent absent, clone auth failed, install failed (registry/proxy), env missing, disk full |
## 17. Milestones & acceptance (resequenced for rev 1.2)
**M0 — Walking skeleton (local mode, both adapters, minimal worktree).** Shell boots on all three OSes with **no system Node** (PATH shim active); open `vite-kitchen` AND `next-app-kitchen` as local folders; **minimal worktree creation ships in M0** — the Next adapter materializes `app/__veect__/**` only ever inside a worktree, never the user's checkout (resolves the materialization/"repo untouched" contradiction); one real component renders through each adapter; protocol round-trip.
_Accept:_ fresh machine (git present, Node absent from PATH) → open app → local fixture → rendered component < 60s; `git status` in the fixture checkout is untouched; a fixture postinstall that invokes `node` succeeds via the shim.
**M1 — Discovery & registry + config.** Full scan, props/tokens panels, watch/HMR, split lazy registries, classification self-heal, perf-500 budgets; JSON config load/validate + **first-run wizard writes it** (workspace-local, offer-to-commit).
**M2 — Canvas + doc + persistence.** Rev 1.1 M2 unchanged (SQLite in app data; JSON write-through + file-wins reconciliation).
**M3 — Worktrees + codegen + checkpoint (local complete).** Rev 1.1 M3, with worktrees in app data + CoW-derived installs; code-owned flip; update-from-base; **committer-identity flow**; **env manager core** (local `.env*` import + injection real Next apps need it here, not M4).
_Accept (additions):_ first checkpoint succeeds on a fresh machine with no global gitconfig; `next-app-kitchen` with a required env var previews after env import.
**M4 — Remote repositories + publish.** Device flow (incl. org-restriction guidance), PAT/SSH (BatchMode + known-hosts UI), partial clone, trust prompt, install pipeline (vendored PMs, ABI policy), env manager remote polish, read-only mode, connect-time write probe, publish + compare-link, failure-class playbooks.
_Accept:_ on a Node-less machine, connect a **private** GitHub repo via device code wizard compose checkpoint **Publish** the compare URL opens a reviewable PR containing the carry set per §10 (byte-identical `veect.config.json` + Tailwind registration, `design.veect.json`, generated TSX); token never appears in any file, log, argv, or `.git/config`.
**M5 — Next.js depth.** Rev 1.1 M4 unchanged (server-component subtrees + refresh, root-layout/middleware handling, `next/font` fidelity, adapter pool/crash recovery).
**M6 — AI compose.** Rev 1.1 M5 unchanged + key-in-Settings flow.
**M7 — Docker + hardening.** Docker image (token auth, volumes, CI export path), doctor complete, Windows pass, pages-router best-effort, docs (connect-your-repo guide, env guide, self-host guide).
_Accept:_ full matrix green; a designer following only the download-page instructions reaches first published branch on their own team's repo.
## 18. Decision log
| Decision | Status |
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| **Standalone app default + Docker additional; npm-in-repo package deferred** | locked (founder, 2026-07-10) register npm `veect` now regardless |
| **Remote-first repo connection (GitHub device flow / git URL / SSH) with local mode** | locked (founder, 2026-07-10) |
| **Config = `veect.config.json`** (schema-validated, app-writable; `veect.preview.tsx` stays code) | locked (founder, 2026-07-10) |
| Electron shell with bundled Node (`ELECTRON_RUN_AS_NODE`); Tauri+sidecar = documented alternative | 🟡 architect's call UI is shell-agnostic web; revisit on binary-size evidence |
| Worktrees + all Veect state in app data, **user's repo never modified** (kills rev 1.1 shields) | rev 1.2 cost: per-worktree installs (pnpm cheap, npm slower, progress honest) |
| Publish in scope: push `veect/*` only; compare-link PR; provider PR-API fast-follow | rev 1.2 (supersedes rev 1.1 "no push") |
| System git required v1 (no bundled git; isomorphic-git can't do worktrees) | 🟡 residual A5 friction guided install; revisit |
| Docker v1 = single-team token; multi-user auth = later cloud layer | rev 1.2 |
| Workspace-trust prompt + per-repo `--ignore-scripts` opt-in; repo-code execution = stated surface | rev 1.2.1 |
| `node` PATH shim + `npm_config_runtime=electron` ABI policy + vendored PMs | rev 1.2.1 |
| Worktree installs derived via CoW/hardlink from the workspace clone | rev 1.2.1 |
| GitHub App auth (per-repo install, refresh tokens) | fast-follow device flow + PAT/SSH ship v1 |
| Commit signing; provider PR-API; GitLab/Bitbucket native OAuth | fast-follow |
| AI in v1 (key via Settings/env) · full Next adapter · SQLite+JSON-in-branch · name Veect | carried forward |
| License / open-core split | strategy, not blocking 🔴 build source-available |
| Design-doc concurrent merge | v1 = file-wins + revision net; deferred |
---
_End of spec (rev 1.2). The editor's visual/interaction contract is `design-brief-veect.md` (see its Connect Repository and Environment screens); the founder's React prototype seeds `packages/editor`; this document is authoritative for everything behind it._