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
43 KiB
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 = framew· 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'sFrameNodegainsviews: 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/codegenand 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):
nodePATH shim. npm/pnpm.binwrappers and lifecycle scripts invoke a binary literally namednode— which doesn't exist on the target machines. Veect injects a shim directory at the front ofPATHfor every child process:node(POSIX) andnode.exe/node.cmd(Windows) exec the app binary withELECTRON_RUN_AS_NODE=1. A fixture with anode-invoking postinstall is part of the A5 acceptance test.- Native-module / ABI policy.
ELECTRON_RUN_AS_NODEreports a Node version but Electron's ABI differs — source builds against nodejs.org headers fail at require-time. Installs run withnpm_config_runtime=electron+ matchingdisturl/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-strictmismatches 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_HOMEto app data, and prefetches at connect. runAsNodefuse 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.
// 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 surfacessafeStorage'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
reposcope 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-runto 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_COMMANDwithBatchMode=yes+ explicit known-hosts policy (fingerprint-confirm UI,accept-newthereafter — first clone must never hang on a TTY prompt). macOS GUI apps often don't inheritSSH_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 duringworktree add/checkout/log, not just clone/fetch/push. (Repo tooling running baregitinside 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
.gitattributesLFS filters, doctor warns with install guidance, and tests assert credential propagation into LFS transfers (GIT_CONFIG_PARAMETERScarries-cinto 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_modulesis CoW/hardlink-cloned from it (APFSclonefile/ 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.