- palette-picker.js: drop the superseded APP/DOCS/INDEX copy tables, the disabled font-rail hoist machinery, LOREM.caption, and an orphan marker - design-context.css + dcx-hierarchy.css: drop the demo-era specimen, radius, and spacing-bar rule groups nothing renders - index.astro: drop an unreferenced boards const - de-export module-internal names (store, bindings, portability, visual-cues); remove picker-server's unused doc-session kill switch and document --doc in its help - palette.mjs: realpath the entry guard so symlinked installs still run the CLI (same guard as visual-cues.mjs) - revert serve-question.mjs copy ride-along; drop the plan/ gitignore entry; dedupe a build-picker comment; file design-context with the system category entries; alphabetize IMPECCABLE_SUB_COMMANDS - new-work.md: drop a same-screen restatement of the comp/world ranking - CLAUDE.md: correct the picker gate baseline to the measured 606 and explain the per-element multiplication; the trims removed the two dead-block font findings - DESIGN-CONTEXT-ARCHITECTURE.md: list portability.mjs in the code layout Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.9 KiB
Design context architecture
How the design interview, the design context document, and everything the user gives us during the seed process are organized, and why.
The problem this layout solves
The design context is one product concept. Before this layout it was scattered across two directories with no owner:
.impeccable/design-interview/held the questionnaire submission, uploaded fonts, staged brand assets, and two runtime files..impeccable/visual-cues/held the cue generation workspace, and also, buried insidecues.json, thecontextobject carrying the entire chat half of the interview plus themodesset. Neither is a generation artifact.- The design context document was not a file at all. It was a client-side render that existed only after a submit, died with its session, and could not be reopened.
- Three scripts hardcoded the storage directory independently, with no shared constant. The agent-side poll CLI treats a missing session file as a clean exit, so a half-finished rename would fail silently and no test would catch it.
Export, import, resume, rebuild, and edit each had to reassemble state from those places with implicit coupling. One canonical store with one code owner replaces that.
File layout, in the user's project
.impeccable/design-context/ the store: the one exportable unit
context.json { schemaVersion, modes, context } the chat half of the run
answers.json the questionnaire submission, flat form-field shape
assets/ brand files the user supplied
fonts/ font faces the user uploaded
cue.png the chosen hero, copied at submit
runtime/ session.json, journal.jsonl, draft.json (gitignored)
exports/ design-context.md, design-context.bundle.json (gitignored)
.impeccable/visual-cues/ the generation workspace, unchanged
brief.md, <slug>.png x6, cues.json (cues + palette only), fonts.json
The reasoning, in the order the decisions were made:
- Durable versus regenerable is the axis. It matches the policy this repo's
own
.gitignorestates: generated sidecars and config may be tracked, but runtime recovery state and local assets stay local. Everything in the store aboveruntime/is a user decision or a user-supplied file, so it is trackable and exportable. The workspace is regenerable process output and stays ignored. context.jsonmoves the chat half out ofcues.json. The context object was never a cue artifact. It rode there because that was the only file the picker served. With its own home it survives cue regeneration, imports without dragging six hero images along, and can be edited by the save flow without touching the generation workspace.cue.pngmakes the document self-sufficient. The Color article renders the chosen cue only when the cue manifest still lists it, which means the document breaks once the workspace is cleaned or in a project that received the context by import. Copying the one chosen hero into the store at submit time costs a few hundred kilobytes and buys rendering independence. The five unpicked heroes stay in the workspace as art direction leftovers.runtime/isolates ephemera. Session discovery, the journal, and the mid-questionnaire draft never belong in an export or a commit. One gitignore line covers all of it permanently.- PRODUCT.md and DESIGN.md stay at the project root. They are the canonical
documents the whole toolchain reads.
context.jsoncarries distilled copies with provenance, never replacements. Edits that touch product truth reconcile back into PRODUCT.md through the agent rather than the store growing a second product record.
Code layout
The shape mirrors skill/scripts/live/, which solves the same class of problem.
skill/scripts/design-context/
store.mjs the only code that knows store paths or writes store files
bindings.mjs the editable-field registry for the document
session-routes.mjs HTTP handlers for the document edit session
portability.mjs the export bundle format and its import validation
skill/scripts/
picker-server.mjs static serving, boot contract, submit, autosave, spawn
picker-doc-session.mjs the session shell: http server, timers, token
picker-doc-poll.mjs the agent's poll CLI
design-context-export.mjs / design-context-import.mjs
picker/scripts/
boot.js one memoized fetch of the boot contract
hydrate.js restoring a previous run into the questionnaire
palette-picker.js the questionnaire; owns running hydration
design-context.js the document; document mode, pending ledger, save bar
Three ownership rules keep this correct as it grows.
store.mjs is the single writer of store files. Server, session, and import all go through it. Every write is atomic: write a temporary file beside the target, then rename over it, so a reader never sees a torn file.
Reads come off disk per request. Files are the truth. Process memory is a cache at best. This is live mode's stat-keyed cache rule in its simplest form, which is all a small store needs.
During a live session, only the session process writes store files. The agent edits its own documents, DESIGN.md and PRODUCT.md, directly. Any answer or context updates it needs to make ride in its reply payload for the session to apply. Without that split, an agent doing a read, modify, write cycle on the answers file can silently drop a change the session wrote in between.
The live session
The document is a working surface, not a report. The patterns below are taken from live mode, scaled to one page, one store, and one agent.
- Append-only journal with pure replay. The session appends one line per
render-relevant event to
runtime/journal.jsonl: applied changes, batch transitions, and request transitions. A booting session replays the file to recover its sequence number and any unacknowledged batch. Lines it cannot use, including records written by an older release that carry no sequence number, become diagnostics rather than failures. - Stage, then apply. Edits accumulate in a client-side ledger keyed by field binding. Re-editing a field updates the new value but keeps the first original, so the record of what changed stays true across repeated edits.
- Delivery that survives a dead agent. One batch is outstanding at a time and is journaled. A poll leases it, a reply acknowledges it, and a session restart re-offers anything unacknowledged. An invalid reply returns a corrective hint while the lease holds, so the agent can fix its own message.
- Server-owned truth, DOM as cache. The state endpoint carries the journal sequence and the current batch. The client compares sequence numbers and re-fetches when the server has moved ahead. That re-fetch and re-render is the hot reload. The count of staged changes before Apply is the client's own, because nothing has reached the server yet.
- One in-flight lock. While a batch is unacknowledged the document's edit affordances are disabled. A re-render triggered by anything else re-applies still-staged edits onto the fresh DOM, so a background event never wipes work the user has not saved.
- Loopback discovery with a per-run token. The session records its process id, port, and token; readers probe liveness before trusting the record; the token, not the origin, is the security boundary.
What was deliberately left out, because each answers a question this surface does not ask: the evidence gathering and verification pipeline and the repair loop (live cannot know which source file a DOM edit belongs to, while every field here has an authoritative address and applying is a deterministic write); server-sent events and reload-resume state (a two-second poll already covers it); poll lanes and priorities and chunking (one agent, one batch); the roots manifest (one fixed path under the project root).
Data flow
SEED
agent stages assets into the store, runs the cue pipeline, writes the
workspace and the store's context.json, then launches the picker
client boots, prefills from a draft or a previous submission, autosaves
submit writes answers, copies the chosen hero, forks the session, exits 0
agent seeds DESIGN.md from the answers, then enters the edit loop
REOPEN
the picker server runs in document mode, reusing or spawning a session
the client renders the document directly, with no submit
the session ending is the agent's completion signal
EDIT
staged changes leave the ledger as one batch, the session applies them to
the store and queues the downstream work, the agent reconciles DESIGN.md
and PRODUCT.md prose and replies, the sequence advances, the tab re-fetches
and re-renders
EXPORT AND IMPORT
the store compiles to a readable markdown document plus a lossless bundle
importing a bundle rebuilds a working store in another project