← Design · c132

plan.md

c132 plan: Native Design Prototyping (draft, before kick-off)

Gate: c130 Phase 1 (the primer) written. Runs beside c131 Phase 1–2 so the first page uses the process. Reader: Mark, then the designers who will mock native screens, then c131's executor.

Why

Design works in a loop: prototype on the design site, experiment in the lab, refine with Mark, push into the real app (the phase-4 lab → qdash port is the current form). The native app breaks the loop's tools: no DOM, no Vue, no Tailwind classes to transcribe, no capture.mts against a URL. This cycle finds the medium and rebuilds the loop so native screens can be mocked, reviewed and pushed with the same rhythm, by designers who may not write Rust.

What we have today

  • Media: Vue mocks on the design site (app/pages/pages/phase*/), Figma through the Figma MCP (/figma-to-vue, /figma-to-component), Illustrator from a page's DOM (/illustrator, scripts/illustrator-export.mjs), the baked lab worlds, the review walk and the cycle atlas (/capture, capture.mts).
  • Tokens: brand/design_tokens.md, the layer's Tailwind tokens (--qp-* RGB triplets).
  • The kit's side: semantic theme tokens (Theme, ThemeColor), many built-in themes, a component gallery, headless tests that render a view, a wasm build (nightly), design guides and coding guides, /skills and /llms-full.txt.
  • Native Shell: a browser playground and gallery; TSX that targets GPUI among others.

Candidate media (the bake-off)

#MediumForAgainst
AVue mocks in a "native skin" on the design site: a theme that mirrors the kit's tokens and chromeour fastest muscle; every existing tool works; reviewers need nothinga second implementation that can drift from what the kit can draw; the transcription step remains
BA stories crate in the c131 repo: one Rust story per page state, screenshotted by headless teststhe real thing; the stories double as tests (R6); no transcriptiona Rust build loop; captures by test harness, not by URL; designers need the toolchain or a CI artifact
CThe kit's wasm build embedded in the lab: the real UI in a <canvas> on a design-site pagethe real thing, reviewable in the browser; capture.mts and the review walk work unchangednightly toolchain; unknown fidelity (text input, tables, fonts) in wasm; build size
DFigma, with a kit-shaped librarydesigners' native tool; the MCP already existsno Figma library for GPUI Kit is known; Figma → Rust has no skill yet
ENative Shell's playground / TSX targeting GPUIa browser playground; TSX is near Vue for our designersa second framework in the stack; its GPUI target's maturity is unknown

Default to test first: C if it renders our screens, else B with A for early ideation only. D is evaluated as an input (a kit-shaped Figma library) rather than the medium. E is noted unless the primer finds it strong.

Deliverables

  1. process.md — the loop for native screens: brief → mock (medium) → experiment (states and worlds) → refine (review stop, Illustrator if hand work is wanted) → push (the port into c131, with the record in the cycle). Who does which step and with what. Written 2026-11-11 (Phase 3).
  2. The token bridge — brand/design_tokens.md → a kit theme file (Qpoint light and dark), generated by a script so the web and native tokens come from one source. Includes the type decision: Inter, bundled (c131 D14, ruled 2026-11-09). Exists: scripts/tokens-to-kit.mjs, built in qnative c2 (2026-11-10), documented in process.md (Phase 3).
  3. Captures for native — a native mode for capture.mts (wasm page, or the stories' screenshots collected into the same manifest), so the cycle atlas shows native screens beside web ones. Built 2026-11-11 (Phase 3): --native <program.json>, the stories per world.
  4. Skills — the kit's skills installed into design's .claude/skills (from the primer), plus ours: a /gpui-story (page state → story + test) and, if D holds, a /figma-to-gpui. Only what the worked example needed. /gpui-story written 2026-11-11 (Phase 3); the kit's skills not installed yet; no /figma-to-gpui (Q2 open).
  5. One worked example — the Inventory page (or Status) taken through the whole loop and into c131. Status, picked 2026-11-11: through the loop in A the same day; the push into c131 handed to qnative c2 (Phase 4).
  6. A design-site page — the process documented where the other processes live (/method or the components docs), with the bake-off record. Done 2026-11-11 (Phase 5): the CLAUDE.md section and /pages/native, with the bake-off page as the record.

Phases

0. Survey

Read the primer. List what each medium needs installed. Pick the page for the bake-off (Inventory: tables, lists, empty and overflow states, the hardest common case). Adopt the named synthetic worlds (ruled 2026-11-11, c131 D15): one composable, useNativeWorld(name), reads ../qnative/crates/fixtures/world/<name>.json by request key, so every native-skin mock draws the rows the app draws and invents none.

Done 2026-11-11. The page is Inventory (ruled): the landing, the Harnesses listing and the harness page, in three states by world. The worlds are copied into the lab (app/data/worlds/, scripts/sync-worlds.sh, the deploy builds here so the copies must live here) and read by app/composables/useNativeWorld.ts: one dynamic import per world (only the named one is bundled), the manifest, and get(route, params?) building the nozzle's key (route?sorted-query, period=all dropped, quote_plus encoding, from types::Request::key and the keys in the files); loaded on the client so the server renders the honest loading state and the payload carries no rows. What each medium needs installed, as found:

MediumA person who mocks needsAn agent that mocks needsCaptures
A Vue mocks in a native skina browser on :3000, or the static sitethe design repo (npm install, Node 22), the layer (@qpoint-io/q-nuxt-layer or NUXT_LOCAL_LAYER), the worlds syncedcapture.mts unchanged (Playwright's Chromium, in the repo); a shot program names the world per shot (scripts/shot-programs/c132-bake-off.json)
B the stories crate in ../qnativea built binary (a CI artifact or cargo run -p stories --show), or the PNGsRust 1.95 or later (Homebrew's 1.99.1; the kit calls std::hint::cold_path), the workspace (cargo build: about 3 min clean, 424 crates; 2–5 s incremental), gpui-kit pinned =0.9.1, Python 3 for ts-to-rust.py and cut-world.py (the latter needs the qdash binary from ../qcontrol for a recut)cargo run -p stories writes target/stories/<story>-<mode>.png at 2× off-screen (Window::render_to_image, no Screen Recording); scripts/publish-stories.py copies chosen PNGs into public/cycles/qnative/ with surface: native
C the kit's wasm build in the laba browsernightly Rust, the wasm32 target, the kit's showcase buildwould be capture.mts unchanged
D Figma with a kit-shaped libraryFigmathe Figma MCP (connected in this session); no kit-shaped library exists to installget_screenshot
E Native Shell's playground / TSXa browserNode and the Native Shell repo at main (its desktop features are unreleased)none into the atlas

1. Bake-off

The same page in A, B and C (and D as an input if a library turns up), each with three states. Captures side by side on a lab page. Time and friction recorded per medium.

2. Mark picks the medium

Review stop. The pick and its reasons recorded here and in CYCLE.md.

Ruled 2026-11-11 (evening), from review.md. B, the stories crate in ../qnative, is the medium of record for a native screen: the real thing, the stories are the page's tests, the captures are solved. A, Vue mocks in a native skin on the design site, is the medium to sketch and to review: for a person without a toolchain and for an agent sketching before Rust. D6 holds: an A mock names its world and the story that follows is checked against it row for row. The review's amendment stands: A is not "ideation only" but the review medium for people. Two more rulings: the chrome (D7) and the mock's lifetime (D8), below.

3. Build the loop

The token bridge, the capture mode, the skills, process.md.

Done 2026-11-11 (evening), at its review stop (review-phase3.md). The native capture mode: scripts/capture.mts --native <program.json> runs the stories binary once per shot with QPOINT_WORLD set and merges the PNGs into the atlas with surface: native, subject: native/<story>, variant: world=<name> (D3); scripts/shot-programs/c132-native-worlds.json took the three Inventory views in all three worlds (13 captures, thread worlds), which closed B's side of the bake-off. The stories read QPOINT_WORLD through a one-line patch, applied in a scratch copy for the first captures and handed to qnative c2, which landed it the same evening (b4cbc23) together with the recut (meridian-day90 = nina-mbp, pilot = priya-x1); the lab re-synced and both media were recaptured across four worlds. capture.mts also gained scale (device scale factor) and clip (one element as the picture) for programs and single shots, so A is captured as the window at the app's 2× (the eleven bake-off captures retaken). The skill /gpui-story (.claude/skills/gpui-story/): a page state → a Story, its test, the per-world capture, and the path from a Vue mock to a story. process.md: the five steps, who does which with what, the two media exactly, the native capture mode, the token bridge documented (scripts/tokens-to-kit.mjs, built in qnative c2; not rebuilt). Not done: the kit's own skills are not installed (install from the repo at the pinned tag when a cycle needs them); no dark variant per world for A.

4. The worked example

Status or Inventory through the loop and pushed into c131 Phase 2.

Mark picked Status (2026-11-11, at the Phase 3 review stop). Status has no page in either medium yet, so it exercises the loop end to end: the brief (status-brief.md), the mock in A on the four worlds, the experiment, the review stop, the push into c131 as a story. The brief names the one fork the page has: the sensor's own shapes (state, resources, effective config, logs; c131 D17) are served by no route yet, so no world carries them and the mock cannot draw them without inventing (D6).

Built 2026-11-11, at its review stop (review-phase4.md): the mock on four worlds with a knob for the states no world carries and degraded derived from the coverage counters; eight captures; the calls S1–S6 taken by default; the push handed to qnative c2 as a story per state.

Ruled 2026-11-11 (evening, from review-phase3.md, before the Phase 4 note was read): Status is the page, through the whole loop: sketch in A on the four worlds, then the story and the page in B, captures per world into both atlases (D9), the page into c131. The six endpoint-only shapes get the bridge of D10 instead of the mock's "not served yet" (S3 overturned) and the state knob narrows to the states no world or bridge carries (S2 narrowed). B is built in this session once qnative c2 has closed (S6 overturned): pages/status.rs, the nozzle requests for the six shapes behind the bridge, the story; committing only its own paths there, small commits with a message to the other sessions on shared files, never a push.

Built the same evening, at its review stop: the bridge per world (app/data/worlds/bridge/), the mock reading it, the mirror (D9) retro-applied, and Status in B in qnative (pages/status.rs, the bridge in types and fixtures, the nozzle, the route, four stories, the composed asset source), captured per world into design's atlas (p4-status-b); the bake-off page's Phase 4 section wipes A over B for seven states.

S1–S6 ruled (c131 D21, the same evening): Status is the landing with the overview's micros below; a "This machine" group (Status, Sensor, Activity) before Inventory; a Sensor page for the five facts in full and the log; an Activity page for sessions over time; the knob kept; the push is qnative c5 on design's first cut. The lab followed: /pages/native/status reshaped, /pages/native/sensor and /pages/native/activity built on the four worlds and captured.

5. Document

The design-site page; CYCLES.md and c130's record updated.

Done 2026-11-11: the loop documented in CLAUDE.md (a section after Sketches and stories, where the other processes live) and on /pages/native (the design-site page), with process.md as the full text; CYCLES.md in line; c130's record left to its own session with a suggested line.

Decisions (defaults)

#QuestionDefault
D1MediumRuled 2026-11-11: B (the stories crate) of record; A (Vue mocks in a native skin) to sketch and to review, for a person without a toolchain and for an agent sketching before Rust. The default had been C if it works, else B, A for ideation only; the primer removed C and the bake-off promoted A to the person's review medium.
D2Token sourcebrand/design_tokens.md stays the source; the kit theme is generated, never hand-edited.
D3Where native captures livethe same manifest and atlas as web captures, with a surface: native field. Built (Phase 3): the native capture mode merges per-world story captures into public/cycles/<project>/manifest.json with surface: native, variant: world=<name>, world.
D4Review stop formthe lab's review walk for C; a captures page for B.
D5Illustratoravailable from captures (raster) only; the DOM-based export doesn't apply to native.
D6Rows in mocksNever invented (ruled 2026-11-11): the lab reads the same named worlds as the app through useNativeWorld(name); a mock names its world the way a story does.
D7The native skin's chromeRuled 2026-11-11: keep the imitation in app/components/native/ (the kit's shell drawn with our tokens), re-checked at each review stop by a wipe against a capture of the real shell; no Vue port of the kit's Shell, no compositing of mocks into shell PNGs.
D8A mock's lifetimeRuled 2026-11-11: when a page exists as a story, its Vue mock stays as the person's medium, naming its world so the story stays checkable against it.
D9Native captures in design's atlasRuled 2026-11-11 (evening, from review-phase3.md): the native capture mode also copies each native capture into design's own atlas under the cycle's thread, the qnative atlas keeping the originals, so a design cycle's visual record shows B beside A without crossing projects. Retro-applied to the worlds thread.
D10The bridge for the six endpoint-only shapesRuled 2026-11-11 (evening): until c131 D17's routes exist (engineering took the ask the same day), the sensor's six shapes get a clearly marked bridge fixture per world beside the world copies (app/data/worlds/bridge/<world>.json, never inside the world files, which stay copies of qnative's), typed from synthetic-data.md's shapes; the footer and the record say it is a bridge; the same bridge feeds the story in qnative.

Clarifying questions

  1. Who mocks native screens? Answered 2026-11-09: both. Mark and agents will each mock various aspects at different times, and being able to mock is crucial to any plan. Consequence: the medium must serve a person without a Rust toolchain and an agent with one; C (the real UI in the browser) and B (stories as tests) are both pursued, and whichever wins, a captured, reviewable artifact per page state is non-negotiable.
  2. Is a kit-shaped Figma library worth building if none exists, or is Figma out for native?
  3. Brand type on native? Answered 2026-11-09: Inter, bundled (c131 D14); the bridge carries the type scale.

Patterns to consider

  • Stories as both the design site and the tests (c131 R6): one artifact, two readers.
  • The wasm build as the lab's embed: the native app's screens reviewed in the browser, captured with the tools we have.
  • Tokens generated from one file into Tailwind and the kit theme: the seam between the two component systems (c130 Risks).
  • The kit's llms-full.txt as the executor's ground, installed once, versioned with the kit.

Qpoint Brand Style Guide