Native App V1
Our V1 of the native desktop app in GPUI Kit: the one-machine slice of qdash (Status, Inventory, Events, Config, Connect) and the tray, two states (local and editable, connected and read-only), the DataNozzle with three sources (`synthetic`, `local`, `plane`), the message channel's ports and bones, stories that are tests, packaged for three OSes, measured against a budget with strobe, handed to the engineers as option B.
New in this cycle: Activity on one machine: the band (sessions per day, tokens and cost by harness), the usage row, Recent Work
Why
The sensor can't carry the web dashboard (c130 Why: under 1% of a core and under 30 MB is the story customers are told, and the bundled UI undercuts it). A native app on the endpoint shows what qcontrol found and how it is doing, without a browser or a bundled web stack. Tyler wants the first shippable release to be this app plus the tray. Mark wants our V1 built first: a simple port of the pages we already have, every page running on synthetic data and ready for real data, the patterns shown by implementing them, presented to the engineers as the prototype they may adopt or replace.
Replicate first, optimize later: this is a port, not a redesign. The qdash data-interface bible becomes Rust traits; the pages keep their meaning; the budget is measured, not promised.
Sketch: The app map and its two states, drawn 2026-11-10 in the design system; Mark's own drawings may replace it.
Objective
Done means: a running GPUI Kit app in ../qnative with Status, Inventory, Events, Config and Connect, the tray absorbed from qtray, local and connected states, the DataNozzle switching synthetic / local / plane for the whole process, the message plumbing with Connect confirmations flowing, a story per page state run as headless tests, bundles for macOS, Windows and Linux with qupdate wired, the budget recorded per release with strobe, and the hand-off (PATTERNS.md, the fixture world, the stories, CI, a recorded walk) presented at G3.
Scope
- Code:
../qnative(cratesapp,nozzle,types,fixtures,stories,messages) - Reads:
../qcontrol/crates/qdash(the API,ui/docs/data-interface.md,ui/types/api/*),crates/qtray,crates/qupdate,scripts/qdash-seed - Design repo: this cycle's record; stories captured into the atlas once c132 provides the mode
- Out: the control plane (c134); training content (c133); the message design beyond the plumbing (c137); notifications beyond Connect unless G2 rules them in; qmap beyond a link (c130 D8)
Key Changes
- stage 1 (2026-11-09): prompt and plan written from c130's split. CYCLE.md written before kick-off at Mark's request (c130 clarifying question 6). Rulings carried from the c130 walk the same day: three data sources (D3, D5), the repo at
../qnative(D1), the spike may start now, ports and bones in V1 (D8), a Status line for grants (D12), the plane connection (D13), the budget measured with strobe (R7). - 2026-11-09, later: Mark ruled the type: Inter, bundled, noted for the degrees of expression it gives (D14). Committed the same day.
- 2026-11-11: Mark ruled on the synthetic-data outline (synthetic data §Rulings → D15, D16): the default world is a quiet laptop (nina-mbp proposed) and priya's is the
pilotworld a tour switches to (switch world · simulate a bad event · watch the local notices and tune the controls: the first native tour, with c137); the world is chosen in the app's settings over one re-open path Connect reuses; the lab adopts the worlds in c132 Phase 0. Open: where the six endpoint-only shapes get their types (recommended: qdash routes typed and generated like the rest). Hand-on to qnative c2 at its review stop: recut and rename the worlds, the settings switch. Nothing in../qnativefrom this session. - 2026-11-11, later: the remaining calls walked here: the six endpoint-only shapes become qdash routes typed and generated like the rest (D17; the ask to qcontrol engineering through c134); nina-mbp confirmed as the default laptop; the tour's simulate step injects the paste scare, the secret-file read or the incident, as one tour or three (c137 question 5). The message to the qnative c2 session was held undelivered by its permission mode, so the rulings went as a hand-on file,
../qnative/cycles/c2.expressive-range/handon-2026-11-11.md, uncommitted there for c2 to fold in. Committed and pushed the same day. - 2026-11-11, evening: qnative c2's two review stops walked here (D18–D20): the brand ruling amended to our layer on GPUI with the kit for the shell; no seed restart; the table stretch's four remaining items now; the five page defaults accepted; atlases released on close to the tray; the tour launcher in a Getting started / Help section, the panel floating and locked right, the guide endpoint on with a token, arrows and Esc. Sent to the c2 session by message. Kick-offs ruled the same evening: c132, c135 (Phases 0–1), c136 (Phase 0), c133 (Phase 0) and c130's
shape.md, each with a fresh agent; c134 waits for the plane engineer's answers. - synthetic data (2026-11-10):
synthetic-data.mdoutlines the fixture world for the five pages: Meridian cut to one machine, the endpoint-only shapes the fleet world lacks, eight named worlds shared with the web demo, where it lives and how it is exported, and what it asks of the plane's data model. - sketch and story (2026-11-10): the app map drawn in the design system (
app/pages/cycles/design/sketches/c131-app-map.vue, beside the DataNozzle sketchc131-data-nozzle.vue), to be captured as/cycles/design/c131-sketch-app-map.pngand referenced under Why; the story moduleapp/data/stories/c131.ts(D1–D14 and R7 as decisions, three outcomes, the credits from1b21a3ce); the section The shape added above. - Phase 2 planned (2026-11-10): Mark called the spike a success and asked for a second phase that tests the kit's expressive range: the whole Inventory section (seven listings, nine details, the landing) in the Qpoint skin, on the data the web pages show, cut to one machine; stretch, the Tour guide and the Data Machine; extra stretch, the table with an expand row.
phase2-prompt.mdholds his words;phase2-plan.mdholds the plan.- Two surveys behind it: the qdash Inventory section field by field, tagged one-machine, changes-meaning or fleet-only; and the Tour, Data Machine and expand-row components, read for what they need from the DOM.
- Found:
- The Data Machine and the expand row are on the main path. The Data Machine is the band and tiles on every Inventory page, and the landing is 20 micros. The expand row carries Tool and Skill invocations.
- The Tour exists only in the lab, and Info View was never built.
- The kit's DataTable has one fixed row height, so the expand row is built on GPUI's variable-height list.
- GPUI's
VisualTestAppContextcaptures a real window off-screen, so native stories become PNGs without Screen Recording. - No Inventory handler takes
?host=(c134 candidate gap row 31 confirmed). - The web's orphan Harness install detail is the page that is already about one machine; on the endpoint it merges into the harness page.
- Six fleet → personal rules. The plan runs in
../qnativeas c2. It bends D6's order and G1, and builds past D5 and R8 as an experiment. - Mark answered the four questions the same day:
- our layer in Rust (
crates/layer, the web names; D5 and R8 hold for V1 until the scorecard); - translate, with the harness pages merged;
- the Tour stretch includes Info View and the
qpoint-guideMCP driving the native app; - three review stops plus one per stretch.
- our layer in Rust (
- Not kicked off; uncommitted.
- Phase 2 kicked off, to review stop 1 (2026-11-10): runs in
../qnativeas c2 (cycles/c2.expressive-range/CYCLE.mdholds the detail).- The world. Meridian cut to priya-x1 (and chen-mba, and an empty world) through qdash's real ingest and handlers, by
scripts/cut-world.py: 128 responses, 533 KB. - The types. Generated from qdash's TypeScript, 72 interfaces. A strict drift test over all 266 responses found five places where the handlers serve what the TypeScript does not say.
- The data layer. The nozzle is generic, with typed repositories that ask qdash's own questions.
- The token bridge. Design
scripts/tokens-to-kit.mjs(c132's deliverable, built here) generates the Qpoint light and dark kit theme andtokens.rsfrom the layer's tokens. - The layer crate. A new
crates/layerowns the skin. - Captures. Off-screen PNGs with no Screen Recording, through
Window::render_to_image. Six are in the qnative atlas withsurface: native: the gallery and tokens, light and dark, the stock kit for comparison, and a type diagnostic. This answers c132's open question of how medium B is captured. - Found and fixed. The bundled variable Inter drew every weight as regular, because GPUI picks faces by family and weight. On Mark's word, Inter 4.1's five static faces (regular, italic, medium, semibold, bold) were downloaded from the rsms/inter release and replace it, for 0.3 MB more. Each weight resolves to its own face (checked by units per em: Inter 4's 2048, not the system's Inter 3).
- The world. Meridian cut to priya-x1 (and chen-mba, and an empty world) through qdash's real ingest and handlers, by
- A Dev section and native component docs (2026-11-10, Mark's asks mid-phase).
layer::demosis the native counterpart of the design site's component pages: one page per component, its scenarios as live, stateful instances, and an event log proving the handlers fire. Ten pages so far, for the skin and the kit's components; ours join in step 3.- The app has a Dev group (debug builds, or
--dev):- Tools: component demos on or off, appearance, the inspector, the data source.
- Components: the docs.
- The story windows got the title bar their drag needed. Headless tests prove the drag and the controls.
- Detail in qnative c2.
- Phase 2, to review stop 2: the first listing and detail beside the web (2026-11-10).
- The layer in Rust: the Data Machine family, the small pieces, the page furniture, the ledger, and the Surface, each with a doc page of live scenarios and a scorecard row. Most rows are at fidelity 3 against the design site and qdash; DataRankedBars is at 2.
- A router with history, the shared period and section anchors.
- The pages: the Inventory overview, Harnesses, and the merged harness page (Sessions, Configured here, Surface), on priya-x1's data.
- Compared with qdash on
:3030at the same width. The shell took qdash's nav width and its sunken canvas; the ledgers' columns now fit; the header's metadata now lines up. The jump pills now dock, after a fix for GPUI recording child bounds before the scroll offset. - Captures: sixteen more in the qnative atlas (threads
p2-data,p2-harnesses). - Detail in qnative c2. Committed (qnative
6a919f4).
- Phase 2: Providers, the provider page and the account page (2026-11-10).
- Built to a spec measured on qdash at the same size.
- Shared ledger and detail plumbing now carries every listing and detail.
- Translated to one machine: users, instances and orgs leave. Tokens takes the account page's Users tile, and dual use is said of this machine.
- Four of qdash's oddities are settled rather than copied.
- Seven captures (
p2-providers). Detail in qnative c2. Committed (qnativecd04f31).
- Phase 2: Models and the model page (2026-11-10).
- Built to a spec measured on qdash; the detail is all-time, as the web asks, and now says so.
- The served
default_oncomes forward as "Default here". - Instances read as harness types; the provider is one ◈ chip.
- Open: one arbitrary provider per model, from the server.
- Five captures (
p2-models). Detail in qnative c2. Committed (qnative21ff05d).
- Phase 2: MCP Servers and the server page (2026-11-10).
- Built to a spec measured on qdash, all-time on the detail as the web asks.
- The configured-versus-called gap is keyed on harnesses.
- The served per-harness config comes forward as a Configuration section.
- The Source filter can pick
both. - Six captures (
p2-mcp). Detail in qnative c2. Committed (qnative5e9a7ee).
- Phase 2: Tools, the tool page and the expand row (2026-11-10).
- The extra stretch is reached: the table expand row at fidelity 3. It is a whole-row toggle with the tinted well and card, growing with qdash's easing, open rows held on stable keys.
- The tool page puts Denied first, with its context, and opens each recent invocation on its arguments and output.
- Eight captures (
p2-tools). Detail in qnative c2. Committed (qnativefda7c25).
- Phase 2: Skills, the skill page and the skill file (2026-11-10).
- The plan's step 3f is in the layer: the editor frame, the file tree, a code block with the web's token table, and the Markdown preview.
- The skill page reads each definition the way the harness does, flags drift in amber, and keeps bundle contents uncaptured.
- Nine captures (
p2-skills). Detail in qnative c2. Committed (qnativeb25979d).
- Phase 2: Repos & Workspaces and the workspace page (2026-11-10).
- The last listing: all seven Inventory listings and their detail pages are now native.
- Freshness runs on one clock, the newest session in the data; clones sharing a folder name are told apart by their parent directory.
- Git stays uncaptured and the page says so. Capturing it locally is cheap, and is open for Mark.
- Six captures (
p2-workspaces). Detail in qnative c2. Committed (qnative044bc32).
- Phase 2: review stop 3, the section, the scorecard, the budget (2026-11-10). Written up in qnative's
review-3.md(/cycles/qnative/c2/doc/review-3).- The scorecard:
- 30 of 36 components drawn on GPUI, 28 at fidelity 3.
- 2 kit as is, none composed from the kit.
- The kit carried the shell, and our vocabulary was drawn. That is the evidence for the c130 D5 / R8 ruling, which waits on Mark.
- The budget: both release builds measured in one session with c1's recipe.
- Binary: 17.9 → 23.2 MB.
- Window idle: 51 → 62 MB private. Tray: 15 → 21 MB.
- CPU, wakes and startup unchanged.
- 116 MB once every page has been shown: GPUI's atlases filling once, not a leak.
- Open, for Mark:
- the brand ruling;
- the one-world side-by-side, which restarts the shared
:7882seed; - the rest of the extra stretch's list;
- five per-page calls;
- the atlases on close to tray.
- Next: the Tour stretch.
- The scorecard:
- Phase 2: the Tour stretch (2026-11-11, Mark: "Start the Tour"). Written up in qnative's
review-tour.md(/cycles/qnative/c2/doc/review-tour); 22 captures, threadp2-tour.- The engine: c125's tour layer (v5 · Panel), native, to a spec measured on the lab.
.qp_id(..)records a region's box in prepaint and adds no layout node; the pages carry ids and nothing else.- The overlay paints Ring, Dim, Ring + Dim and Fade from the same frame's boxes, with no lag.
- Callouts are placed by the web's obstacle score; the glide is the web's.
- The native tour, "What's on this machine":
- tours as serde data, mirroring c123's type;
- 7 stops and 9 steps over the Inventory, with live facts and one detail page reached through a fact;
- spots, Float or Split, minimize, follow the page.
- The MCP guide drives the window:
- the 16
qpoint-guidetools served from inside the app, on loopback; - a native app map (16 pages, 108 regions, 13 concepts) whose rows come from the data;
- open-page shows the way in the sidebar first; no tool presses anything.
scripts/guide-check.pypasses 117 of 117 against the running app.- A one-prompt run by a real harness is owed: the CLI's login had expired.
- the 16
- Info View, native first: the footer's Info explains the region under the pointer from the same map.
- Open, for Mark:
- a launcher on the pages;
- the 488 px page beside the dock (grow the window by 420, or fix the bands' overflow);
- the endpoint on by default;
- keyboard.
- Waiting at this stop (D15–D17's hand-on): the worlds recut and renamed (nina-mbp the default laptop, priya's the
pilotworld) and the settings switch.
- The engine: c125's tour layer (v5 · Panel), native, to a spec measured on the lab.
- developer mode split out (2026-11-11): Mark's next objective for the app (the live viewer replicated and improved, the monitor TUI replicated, the data switch admin'd in the app, three config-file modes, a gate that can switch developer tooling off or ship without it, instruction pages for hooking a live sensor) was planned here as a thread and ruled its own cycle, c138 Developer Mode, with qnative c3 as its twin. It amends D6 (Events curated, Live raw;
plan.md) and R2 at the page level (two positions on the Sensor page), and takes D15–D17 as given. V1's Status and Connect pages stay here. - Phase 2: Mark's rulings D18–D20, built (2026-11-11, qnative
71dd21a). Built on branchc2/rulingsin a worktree beside c3 Developer Mode, rebased onto its seam (2f66b4b) and fast-forwarded into qnative main. Record: qnative c2 CYCLE.md, and "Ruled" sections onreview-3.mdandreview-tour.md. 13 captures, threadp2-rulings.- The table at full fidelity:
- a sticky header, pinned under the band on detail pages;
- keyboard (↑ ↓ ⏎ Esc) and AccessKit roles, with two GPUI tests;
- columns sized to their content;
- the well eased to its measured height, and again when nested rows open;
- the quiet nested look, and the new-row flash.
- The atlases, measured first: GPUI frees the GPU atlases with the window. The app now also releases its caches and the allocator's free pages when the last window closes. The tray after a walk and a close is 48 MB, against 22 for a tray-only start; the rest is driver-held GPU memory and some heap.
- The tour:
- the panel floats at the window's right edge, never docked, and says when a step's highlight is under it;
- ← → and Esc while the panel has focus;
- the guide's endpoint takes a per-launch token (401 without it; 117 of 117 checks with it);
- Getting started (Help) in the sidebar holds the launcher and the connect line with the token.
- D15–D16:
- the default world is nina-mbp's quiet laptop (
meridian-day90recut, 119 responses); priya's cut is bundled aspilot; - worlds are parsed per name on first use;
- the world is chosen in the tray's Synthetic world submenu, remembered in
settings.json, and swapped in place over one reopen path (a data generation the page, the footer and the facts follow) that Connect can reuse. - The lab's
useNativeWorld(c132) now reads nina's rows undermeridian-day90, in an unchanged file shape.
- the default world is nina-mbp's quiet laptop (
- Waiting:
- the real-harness run, once the CLI login is refreshed;
- D17's routes, with qcontrol engineering;
- the violation tour, with c137.
- The table at full fidelity:
- Phase 2 complete: qnative c2 closed (2026-11-11, qnative
5f39b6d; Mark ruled it closes once the harness run is done).- The harness run: Claude Code (Sonnet 5.5, headless) connected to the app's guide with the launch's token. From one prompt it started the tour, opened Models by way of the sidebar, pointed at the model list and answered from the live rows. Ten turns, 18 s, no errors, nothing pressed (qnative
harness-run.md). - c2's Outcome: the kit stretches as far as our own layer drawn on GPUI (D18). The section, the budget (24.2 MB binary, ~62 MB at rest, 48 MB in the tray after a close), the Tour stretch and D15–D20 are built. 122 captures.
- The close: qnative's
HISTORY.mdstarted; c2's Visual record curated in qnativedocs/images/cycle-history/. - Hand-ons:
- D17's six routes go to engineering, through c134;
- the violation tour goes to qnative c4, the messaging slice (c137 Phase 3);
- the Status page comes from c132 Phase 4 as its worked example;
- qnative c3 (Developer Mode, c138) is in the tree.
- Next for c131: the V1 build, from c130's
shape.mdand c0's name.
- The harness run: Claude Code (Sonnet 5.5, headless) connected to the app's guide with the launch's token. From one prompt it started the tour, opened Models by way of the sidebar, pointed at the model list and answered from the live rows. Ten turns, 18 s, no errors, nothing pressed (qnative
- 2026-11-11, night: c132's Status brief walked with Mark (D21): Status is the landing (the overview's micros move onto it; Inventory keeps its index); a Sensor page carries the five sensor facts in full, honest until D17's routes; an Activity page shows sessions over time as the plane app does; the log viewer stays Developer Mode's; the lab's state knob stays chrome. Sent to qnative c5 (which builds Status, Sensor, Activity in that order) and to c132 for the lab mock.
- 2026-11-11, G1: shape.md accepted; D22 records the two states' sentences, the connected-state read rule and the working budget targets. Sent to qnative c5 (Status says the sentences) and c4.
Outcome
Not started.
The shape
Documents: the plan · Mark's prompt · synthetic data · Phase 2 plan · Phase 2 prompt
The problem. Tyler sketched the app as a list of tabs: diagnostics, the event viewer, a local inventory, a config that goes read-only once connected. As a list it reads like the web dashboard again; the one-machine scope and the one guard on Config only show once it is drawn.
The core idea. One window, five pages in the order they are built, and two states that differ in exactly two things: what the app reads and whether Config opens. The app says which state it is in, in one sentence, and the footer names the source. The tray is the same process with the window closed. Every page sits behind the DataNozzle, so the shell, the tray and the three sources were proven in the spike before any page was designed.
The pieces.
- The window: Status, Inventory, Events, Config, Connect, and the footer that names the source (
../qnative/crates/app). - The tray: status at a glance, Open app…, the tap toggles, tray-only mode (
crates/app, ported from qcontrol'sqtray). - The two states: local (reads this machine, Config editable) and connected (reads the plane, Config read-only), behind one guard (R4).
- The DataNozzle: one switch, three sources, a repository per resource (
crates/nozzle; drawn in its own sketch). - The types: one per served shape, written from qdash's handlers (
crates/types). - The message bones and the inbox store (
crates/messages, c137); the grant line on Status (c136); the stories crate (crates/stories).
The pattern we follow. The bible's Transport ported as the DataNozzle (R15 and R25 as Rust traits); the demo is the test (one story per page state, run headless, R6); purpose plus protocol (a page asks a repository, never a transport); replicate first, optimize later (a port of pages that exist); the budget measured, not promised (strobe, R7).
What stays open. Socket API or SQLite for local (Q2); the tray on Windows and Linux (Q3, G2); the budget numbers (Q4); Connect in V1 as a mocked flow (Q6); which messages flow beyond Connect (Tyler, G2); the inbox's place beside the five pages (c137 Phase 3); the names (c0); shape.md accepted (G1).
Visual record
28 captures — heroes, page slices, and full-page renders. Click to view.
Phase 2 · Activity and the session page in the real app, per world, mirrored from the qnative atlas (D21)
2026-11-11· 12 captureslatest











Phase 2 · Status and Sensor in the real app, per state and world, mirrored from the qnative atlas (c132's push, D21)
2026-11-11· 12 captures











Sketches — the structures, drawn in the design system
2026-11-10· 4 captures



Artifacts
- initial-prompt.md
- thread: phase2 — prompt + plan
- initial-prompt.md
- plan.md
- synthetic-data.md