5 started, 3 closed, 18 carried live.
Busiest record: c92 qcontrol New Pages (15 entries) · 49 carried cycles have gone quiet · layer v0.9.13 → v0.9.20
DataMachine — the base-component + curated-preset pattern qmap arrived at, now a named layer idea
Commits
143 commits across 10 repos, on each repo's checked-out branch.
Cycles
Started 5
Stand up the c90 qcontrol-preview pages in the real qcontrol app (qdash UI) — phase 1 the combined Teams, Users & Devices listing, alongside a qdash global filter that replicates app.qpoint.io's behaviour, then the remaining detail/listing pages
Build the DataMachine composition component and document the base-component + curated-preset pattern it embodies
Assemble seven page mocks (Teams & Users, User Details, Team Details, Sessions, Session Details, Devices, Device Details) behind shared app chrome that mimics the real qcontrol shell, ahead of integration with the real app
First-pass mocks for Teams listing header upgrade and net-new Sessions listing page in phase1 dashboard style
Extract app.qpoint.io's global filter into q-nuxt-layer as presentational components (Phase 1), then design and build a v2 segmented-bar redesign (Phase 2).
Still open 18
Redesign /cycles into a living Design status surface - working now, decisions, seeds, blocked work, abandoned ideas, and the existing atlas
First-pass mocks for all 17 Phase-1 dashboard pages (Linear PRO-19…35) on one unified fixture world — the simplest literal execution of the listing → expand → detail → actor-web pattern
Declare the nature of each conversation at triage (source type × call type), tag every observation with the speaker's perspective (first-hand / relayed / internal), enforce evidence ceilings and a code-computed validation gate, extract session records (decisions / todos / ideas) from working sessions, and formalize the conversations → insights → brief hand-off into the design machine.
Design refinement of qcontrol's Inventory and Users/Devices pages, driven by Mark Peterson advisor call
qproxy MITM truncates streamed brotli response bodies — client apps show Decompression error: BrotliDecompressionError
Design the four product reports (Compliance, SLA Insights, Inventory, Board) and the fleet overview landing page as a summary of them.
Create/configure modal for 11 connectors with per-integration custom UI and step-by-step operator guidance
Convert "Looking In" thesis to Kindle-compatible formats (EPUB + print-replica PDF) while preserving images and foundational status
Refine the qdash /teams design — composition from c61 concepts, mapping workspace as the pre-SCIM write path, slim spend overview, credentials relocated
Make push to main reliably deploy the design site to Cloudflare Pages *(2026-09-25: reframed as "make the build a manual local step", see PICKUP.md)*
Explore and integrate Avoma notes data into extraction pipeline and dashboard visualizations.
Refine how extracted data is displayed in the dashboard and improve extraction quality where gaps exist.
Add Google OAuth to restrict dashboard access to @qpoint.io team members.
Methods & tooling retro — evaluate how we work against the design methodology, restore order, modernize the skill/agent tooling, and make the philosophy load-bearing
Build-partner portal — research, ideation, and a plan for a page that makes partners insiders
Align the qcontrol agent-monitoring dashboard to specific personas and find market alignment — hypothesis-first, validated against calls later.
Closed 3
Build the DataMachine composition component and document the base-component + curated-preset pattern it embodies
DataHistoryGraph component family (q-nuxt-layer) — a pictogram-style
transcript/* component namespace (q-nuxt-layer) for rendering an agent chat session — messages, collapsible tool-call bursts, and a ChatGPT-style hover-expanding turn navigator.
Pages & components
What this week's captures picture — 5 pages, 7 components · 10 first captured this week. Each links to its evolution: every moment it was shot, across cycles.
Pages 5
Components 7
Log
Key Changes entries dated inside the week, from each cycle's record — newest first within a cycle.
c92 qcontrol New Pages
working-nav + people-pages planned (2026-09-20): Plans written, nothing executed (working-nav-prompt.md/-plan.md, people-pages-prompt.md/-plan.md). Verified before planning: the sidebar's NAV literal + useExperimentalNav singleton are the templates for the new toggle; layer NavVerticalItem takes a fallthrough class, so the nav-working hook needs no layer change; every detail page needs an id in the query and renders an empty shell without one, so Working's detail links must resolve a real example — fleet /api/surface + /api/agents + /api/installations + /api/sessions cover every kind in four requests. For the people pages: teams.vue already routes every number through typed rows (PersonRow/DeviceRow/TeamRow) from lib/people-rollups.ts, which is the swappable-data seam; seven inbound /teams links (surface-rollups, sessions detail, index checklist, nav) need retargeting; the design-site phase-1 rows are a different shape, so synthetic data is qdash-shaped (lib/people-sample.ts). Components stay qdash-local until a layer-extraction thread (arbitrary-value classes, ProjectedBadge, qdash routes are the seams). Each plan ends with the decisions to confirm.
working-nav + people-pages executed (2026-09-20): Both plans run end to end on qcontrol feat/new-pages (twin record qdash cycles/c15), every decision on its default. Working nav: useWorkingNav (localStorage, off by default) + a footer toggle above Experimental; a WORKING section after PROVE lists all thirteen listing pages and the eight detail pages; lib/working-nav.ts (vitest 6) resolves a real example per detail kind — the busiest row from fleet /api/surface, /api/harnesses, /api/sessions fetched once on toggle-on — and kinds with nothing observed fall back to their listing, dimmed; nav-working hook class, italic only. People pages: TeamsOverview / UsersOverview / DevicesOverview lifted from the combined page as presentational components (rows in, events out, linkTo function prop from lib/people-links.ts so the front site can render them link-less), usePeopleData holding the feeds + rollups + filter registration + /api/teams write path, lib/people-sample.ts as the synthetic data source in the rollups' exact row shape (vitest 2); /teams → /teams-users-devices (git mv, composes the three; Devices Sensored moved into a four-card Devices band with legends below the rings, a Teams band added, Users gained Active · 30d), solo /users, /devices, /teams (row → /users?team=; stray ?user= forwards); seven inbound /teams links retargeted. Verified headless on an isolated seed instance (:7880 — the :7879 seed wedges at 900% CPU under the dev proxy's polling; noted, not fixed): console clean on every page, deep links and row clicks land where the plan says. npm test 241 (29 files); dist regenerated. Not done: component mount specs (qdash's vitest is node-only — headless instead), design-site demos and layer extraction (their own thread, once the front-site framing exists), archive captures (kept in /tmp/c92, as the earlier c92 threads did).
listing-viz planned (2026-09-20): Plan written (listing-viz-prompt.md/-plan.md), execution handed to a separate agent in the same session with every decision on its default. Surveyed all thirteen listing bands against the layer's data components and the dataviz form heuristic: eleven of thirteen bands are number-only, and three of the four existing devices are 2-slice donuts (the form the heuristic rejects for a single ratio). Catalogued each category's information types by column — identity · magnitude · composition · outcome · time · reach — and found composition is the missing column everywhere while a real time axis exists only on Sessions (started_at) and the fleet's /api/trends series, so trend sparks are honest on exactly those. The one missing note is a segmented bar (hand-rolled once on the fleet index, needed on nine pages) → DataSegmentBar in the layer, sharing the donut's token-role resolver and the history graph's validated palette via a lifted data/palette.js.
timeframe-always planned (2026-09-20): Plan written, nothing executed (timeframe-always-prompt.md/-plan.md). Verified before planning: the layer's v2 Filter carries timeframe/timeframeValue/timeframeOptions props but never renders them — c88 moved timeframe selection into the Config modal — so the only registrant (Sessions, default all) compensates with a hand-placed period trigger bar; useGlobalFilter.period is per-registration and not persisted; the backend's "universal scope" (period_span · time_pred · window_cutoff, anchored to the data's MAX timestamp) is honoured by exactly four endpoints (costs, dlp, security, sessions) and none of the listings; models and workspaces rows carry no timestamp at all, so client-side windowing can't be honest. Read as three requirements — always visible, already applied, honoured — the plan puts the pill in the layer as FilterItem with a new removable prop, makes the period shell-owned (DEFAULT_PERIOD = '7d', URL omits the default, localStorage follows), and adds period to every listing endpoint so magnitudes come out in-window. Seven decisions to confirm, headed by the default window (7d vs 24h vs 30d). Confirmed same day, all on their defaults; Mark asked that server-side windowing be assumed necessary and thought through, so the plan's §C became a per-endpoint spec under four rules — one fleet anchor (MAX(events.observed_at), replacing the per-table anchors), activity facts window while at-rest facts don't, magnitudes are windowed at the fact grain (never "in-window runs × all-time cost"), and presence follows existence (activity-only entities drop out, at-rest ones stay with zeros) — plus the consequence for pages: coverage denominators are fetched all-time, activity feeds with the global period.
detail-pages planned (2026-09-20): Guide and plan written, nothing executed (detail-pages-prompt.md, -guide.md, -plan.md). Read all six old pages against the three model pages and inventoried every detail endpoint's payload: all six are all-time with no day series (so User Detail's chart-beside-band has no honest data anywhere yet); the shared correlated_agents builder zeros calls on three pages and tokens/cost on the other two; tools computes its success denominator but doesn't emit it; models.tokens excludes reasoning; findings attach to agents only; /api/surface has no harness axis; MCP Server's per-tool denied is served but no longer rendered since Surface replaced its Tools table. The recurring story on four pages is at rest vs at runtime (installs config vs observed runs) and none of the old pages puts the two side by side. The guide catalogues the model pages' patterns A–Q, gives each page its purpose, three-reader value, holes, numbered objectives and a four-tile DataMachine band, and records a six-item backend backlog (per-scope policy block and day series first). The plan sequences one groundwork thread (three backend fixes, an ActorHeader extraction, lib/detail-bands.ts, a shared chip/glyph table, an Actor detail frame pattern entry) then six page threads Model → Workspace → MCP Server → Tool → Harness → Machine, and ends with eight decisions. Mark confirmed 1, 2, 4–8 on their defaults and overruled 3: Device Details stays its own later Phase-3 thread (the c90 device mock is still in manual design), so Machine Detail is rebuilt on the actor frame without borrowing from the mock.
listing-viz executed (2026-09-20): listing-viz-plan.md run end to end, every decision on its default. Layer (released as 0.9.16): DataSegmentBar (data/SegmentBar.vue) — values in, normalized; 2 px surface gap; per-segment hover title; legend below / beside / none (title + value in text tokens); no colors → the three validated categorical slots with a fold into "Other" past the third uncolored part (explicitly colored parts never fold); ordinal → one grape hue stepped light → dark, validated with the dataviz validator in both modes (light #BBA5FF · #895AE8 · #5E29CC · #32146E, dark #E5DAFF · #BBA5FF · #895AE8 · #5E29CC); color: 'outline' for the presence vocabulary's "stopped"; empty → hairline track. data/palette.js lifts the donut's token-role resolver + palette, the history graph's categorical slots and the ordinal ramp into one file (history-graph/palette.js re-exports; donut and history graph render byte-identically). data-val / data-total / data-value hooks on DataMachine / DataSegmentBar so headless checks read raw numbers. Design site: DataSegmentBar demo (empty · minimal · typical · overflow-fold · ordinal · zero part · outline · beside · none-with-shared-key · inside a DataMachine) + registry entry; DataMachine demo gains the Composition / Ranked share / History presets with copy snippets; Listing Overview Band app pattern carrying the four rules and the honest empty state. qdash (feat/new-pages, twin cycles/c15): lib/actor-bands.ts grew shareOf / segmentsOf / byDay and per-band *Segments / *Top / byDay (+ toolsBand ok · failed · denied with judged = calls − denied, since judged isn't served); lib/people-rollups.ts gained intensity / coverage segments, shadow-by-harness and teamsBand; RankedBars composition (UxPercentBar rows, share of the whole); npm test 262 (was 241). All thirteen bands re-drawn per the plan's table: three 2-slice donuts became meters (Machines, Harnesses coverage) and one a three-part outcome donut (Tools); Sessions gained the full-width sessions-per-day history (clean · flagged, window follows ?period=, caption names the 200-row cap); fleet index presence → DataSegmentBar, At-risk :spark from the real /api/trends.posture[].crit (a flat zero line on the seed — honest), five footprint sparks + rampTo + the ProjectedBadge line retired. Verified headless on the isolated seed (/tmp/c92/listing-viz.mjs): 149 checks, 0 failures over 22 page loads — 13 pages, 9 filtered (?kind=cli, ?kind=mcp, ?source=observed, ?provider=anthropic, ?posture=Critical → empty state, ?posture=Elevated, ?period=7d&status=running, ?period=30d, ?team=Payments), both themes (token-role fills flip): console clean everywhere; every bar's parts sum to its card's value or to the whole its provenance names; ranked shares ≤ 100 and match the provenance's "top N = X %"; Tools donut counts = Calls; per-harness rows sum to installs; Sessions history lanes sum to the table rows under every period. Design demos render clean-console (the DataMachine page's 7 hydration warnings pre-exist — the demo's random-walk spark data). Consumer lockfiles refreshed, qdash dist/ regenerated. Deviations, all recorded here: Models' Tokens headline is now every token kind (in + out + cache read + cache write) so the four-part bar cross-foots — the table's column stays in + out and the provenance says so; Sessions' Flagged bar counts sessions by worst flag (DLP > sensitive > denied) so it sums to the card, the provenance carries the event counts; DataSegmentBar grew a width prop (a #right flex child has no intrinsic width) and honors legend="none" for the per-harness stack, which owns one shared key; the people-page bands stay fleet-wide under a team filter (the people-pages ruling — cards describe the fleet, the table the filter), so those devices cross-foot with the provenance's "of N seen" rather than the filtered table. Not done: a per-category daily series (plan decision 3, the next thread); archive captures kept in /tmp/c92/listing-viz/, as the earlier c92 threads did.
day-series planned (2026-09-20): Plan written, nothing executed (day-series-prompt.md/-plan.md) — the listing-viz follow-ups made concrete. Verified before planning: the three timestamped fact tables (llm_usage, tool_calls, runs ⋈ agent_instances via run_meta_map) cover every listing axis; /api/surface's scope resolver and /api/trends' day axis + dedup key are the pieces to reuse; detail-pages' backend commit already emits judged/ok/failed and reasoning_tokens, so the listing bands' two recorded deviations now disagree with Model detail and are reconciled first (§A, unsequenced); everything else waits for timeframe-always's fleet_cutoff (executing in the other session as this was written — cost.vue already reads gf.apiUrl). Eight decisions at the end, defaults stated.
detail-pages executed (2026-09-20): Plan run end to end, every decision as ruled (detail-pages-guide.md, -plan.md; twin record qdash cycles/c15 §detail-pages). Groundwork: backend web.rs — correlated_agents callers pass both maps (real calls / tokens / cost on every page's Agents ledger), /api/tools emits judged / ok / failed, /api/models emits reasoning_tokens (tokens = in + out + reasoning), /api/surface gains a policy block per scope, workspace runs end at their last observation; cargo test 58. Layer: ActorHeader + ActorHeaderCell extracted from agent detail's inline header (decision 2 — layer, with a demo; shipped in 0.9.17 alongside timeframe-always's Filter change), then DataMachine showFullNumber and AgentAvatar size as 0.9.18; consumer lockfiles refreshed. qdash: lib/detail-bands.ts (six pure band functions, vitest 20), lib/entity-chip.ts (the session page's glyph + chip vocabulary, shared); agent detail adopts ActorHeader with no visual change. Design site: both components documented (registry, adminProps demos), an Actor Detail Frame pattern on /patterns/app whose demo cross-foots. Pages: six fresh-context agents, one per page, each holding the guide's §4 section and a shared brief (/tmp/c92/detail-pages/BRIEF.md) — Model → Workspace → MCP Server → Tool → Harness → Machine, all on the frame, nothing old dropped (What the first screen doesn't carry sits under a "More detail" rule). Review caught two rule breaks before commit: Spend tiles abbreviated to "$2k" (→ showFullNumber) and the model token bar's parts included cache and didn't sum to the headline (→ parts are in / out / reasoning; cache is named in provenance). Verified headless on a fresh seed instance (:7881, rebuilt binary) — populated / empty / not-found / dark, console clean, band ↔ ledger pairs checked per page, link spot-checks; npm test 287. Seed gaps (no skills, configured-only servers, shadow installs, default_on) covered by specs and route-fulfilled fake payloads. Machine was rebuilt on the frame alone per decision 3 — Device Details remains its own later thread. Follow-ups in the twin record (listingTo adapter branch, modelsBand reasoning part, DataMachine provenance slot, detail pages + timeframe).
timeframe-always executed (2026-09-20): timeframe-always-plan.md run end to end (qdash ca04a2ee + record c15; layer 0.9.17 — overtaken minutes later by detail-pages' 0.9.18, which is what qdash pins). Layer: FilterItem.removable and Filter's permanent when: column, last before Config, rendered whenever timeframe is on and options are given — the bar is never in its empty state on such a consumer; timeframe=false keeps the "Add Filter" pill. Design site: Filter demo's "Empty" scenario became "Timeframe only — the always-in-place state" plus a timeframe=false scenario; FilterItem demo gained removable=false (with formatVal option labels) and the removable admin toggle; registry entries for both (props formatVal / roundLeft / removable documented, timeframe-only state); qcontrol/teams mock flipped to the pill (page-registry note); all four pages render the pill headless with a clean console (/tmp/c92/timeframe-design.mjs). qdash shell: DEFAULT_PERIOD = '7d', period shell-owned in useGlobalFilter (URL omits the default, localStorage follows, hydrated on register with URL winning), keys: [] as a period-only registration, gf.periodLabel / apiParams / apiUrl(path, keys) — the picker because ?host= is /api/machines' detail mode, so a listing forwards only the keys its endpoint reads; 8 new specs. Pages: eight listings + fleet index fetch through gf.apiUrl and label their bands with the window; fleet index's seen7d retired for the windowed count with an all-time denominator; Sessions' period trigger bar and Cost's tab group retired (same ?period=, deep links hold); Findings / DLP period-only; people pages fetch rosters ?period=all and activity with the global period, row fields window-neutral, Overviews take a period prop, intensity keeps its fixed 30d feed; the solo Teams page registers period-only so its windowed numbers carry the pill. Backend: fleet_anchor (MAX(events.observed_at)), fleet_cutoff / cutoff_pred / lifetime_pred / run_cost_map_in; period on agents, inventory, models, tools, mcp-servers, workspaces, machines, harnesses under the four §C rules; costs / dlp / security / sessions / stats-24h moved onto the fleet anchor; agents_value / costs_value extracted; one windowed test per body + the §C.2 invariant (agents_cost_cross_foots_with_costs). Where the plan was wrong: harnesses already carried agents / runs / tokens / cost / last_active (the plan said the row had no activity fact) — windowed those, identity read from every run on record so an installed adapter keeps its name; time_pred replaced (its space-form datetime() literal let the whole cutoff day through against T-form timestamps — cutoff_pred writes a T-form literal); a third index was needed, tool_calls(run_id) — /api/agents took ~5 s on the seed on both binaries (its run_id = ? OR entity_id = ? subselects scanned) and now takes 0.1 s; useApi dropped a path-change refetch that arrived mid-flight, so the hydrated period was served one poll late — it now queues (a real race the plan didn't foresee); PersonAgent's windowed fields are windowSessions / windowTokens (sessions was already the all-time roster count). Verified headless (/tmp/c92/timeframe.mjs): isolated seed :7880 on the rebuilt binary — 53 checks, 0 failures: pill with no ?period= on all 16 registered pages, Not Applicable on the unregistered ones, band = table on every listing at 24h / 30d, Σ agents cost = Cost total at 24h / 7d / 30d (66.07 / 331.44 / 1926.20) on the API and in the DOM, all-time denominators constant across windows, persistence across a breadcrumb navigation / reload / bare URL, ?period=all deep links, back-to-default clears URL and store, dark theme; console clean apart from two DataRankedBars duplicate-key warnings under 30d that belong to the concurrent day-series thread's layer component (keys by label). Live :7878 (old binary, root-owned): 30 shell-only checks pass; it needs the restart for windowing. npm test 290 (+8), cargo test -p qdash 68 (+10). Not done: dist regeneration (PR time); detail pages still Not Applicable (detail-pages decision 6 inherits ?period= + /api/surface period); archive captures kept in /tmp/c92/ (timeframe-*.png, timeframe-design-*.png).
day-series §A executed (2026-09-20): day-series-plan.md §A run end to end, every decision on its default; §B–§F wait for timeframe-always (landed the same day, qdash ca04a2ee) and detail-pages — start §B from the branch tip. Twin record qdash cycles/c15 §day-series §A. Reconcile (qdash lib/actor-bands.ts, vitest 290): modelsBand.tokens is the served field (input + output + reasoning — the detail-pages ruling), the composition bar's parts are those three and sum to it, cache read / write are named in the provenance (+2.87B cache read · 324.1M cache write · 83 % of all tokens served from cache), never drawn — Models listing and Model detail now share one definition (the listing keeps a zero reasoning part in its legend where the detail omits it, so the legend states the definition); toolsBand reads served judged / ok / failed / denied, the calls − denied derivation and its comment are gone, success = Σ ok ÷ Σ judged (the definition Tool detail's donut draws); specs assert parts sum to tokens and ok + failed + denied = calls. Layer (0.9.19, then 0.9.20 minutes later): DataRankedBars (data/RankedBars.vue) — qdash's RankedBars lifted verbatim plus layout: 'inline' | 'stacked'; stacked puts the label on its own line above a full-width bar with the share at the right end in text tokens, so id-shaped labels never truncate; 0.9.20 keys rows by index + label after timeframe-always's headless run caught duplicate-key warnings under 30d (two sessions by one user · agent, two workspaces named alike). qdash's RankedBars.vue is a shim for one release; Models / Workspaces / Machines Spend and Sessions Tokens call DataRankedBars layout="stacked" — the three third-column Spend sites at :width="150", because a 190 px device beside a 4.2k USD value overflows the card (the inline version had always overflowed there too, invisibly, since the bars have no track and the labels truncated); Teams / Devices / MCP Servers / Tools / Agents stay inline through the shim. Design site: DataRankedBars registry entry + demo (both layouts, empty em-dash, the same long paths inline vs stacked, people with mono off, inside a DataMachine); the DataMachine Ranked share preset and its copy snippet re-pointed to the component. Cycle-manager: concurrent-threads paragraph under Thread Documentation Convention, synced to the six sisters (bob-wire, market-analyst, app.qpoint.io, qflow, qmap, qpixel), each committed. Verified headless (/tmp/c92/day-series-a/verify.mjs, captures there): 192 checks, 0 failures over 48 page loads, both themes, console clean — Models Tokens headline = Σ served tokens = Σ in + out + reasoning, bar parts input / output / reasoning sum to it, cache in the provenance only, tokens-by-model bar sums to it, every Model detail Tokens tile = its listing row and the seven sum to the headline; Tools Calls = Σ served calls, Outcome donut counts = Σ served ok / failed / denied = Calls, headline = Σ ok ÷ Σ judged, five Tool details' Calls / Denied / Success tiles and judged · ok · failed provenance = their rows; every stacked label = the rollup's top 3 and renders untruncated (also under ?period=30d and ?period=all); the inline shim pages render. On seed :7881, not :7880 — :7880 runs a binary from before detail-pages and serves neither reasoning_tokens nor judged / ok / failed, so §A can't be checked there; :7881 is the rebuilt binary on the same seed db. Consumer lockfiles refreshed to 0.9.20 and committed (design, bob-wire, app.qpoint.io, www.qpoint.io, qflow, qdash). Concurrency, as it happened: the sibling timeframe-always session committed the four listing pages (ca04a2ee) minutes after this thread had edited them, sweeping the Tokens-card reconcile and the four call-site switches into its commit — the very interleaving the new rule names; noted in both threads' commit messages, nothing to undo. The design registry (shared with timeframe-always's uncommitted edits) was staged as this thread's hunk only (/tmp/c92/day-series-a/stage.mjs: HEAD blob + substitution → update-index). Not done: §B–§F (sequenced); the RankedBars.vue shim deletion (next release); the Listing overview band pattern still hand-rolls its ranked rows (§F); dist not regenerated (§A doesn't ask; PR time); archive captures kept in /tmp/c92/day-series-a/.
actor-listings + surface-section (2026-09-19): Plans written, nothing executed. Surveyed all 17 /inventory pages and the design mocks: no listing has an overview today, filter state is four idioms (useAgentFilter chip, posture/kind chips, UxTabGroup period, none), and Surface already exists three times on DataSurfacePanel (fleet index, agent detail, user-v3) with four more hand-rolled look-alikes on detail pages. See actor-listings-plan.md, surface-section-plan.md — each ends with the decisions to confirm.
actor-listings + surface-section executed (2026-09-19): Both plans run end to end on qcontrol feat/new-pages (twin record qdash cycles/c15). Actor listings: all eight /inventory/* listings and the fleet index register with the global filter and carry a three-DataMachine overview cross-footing with the filtered table (lib/actor-bands.ts, vitest); key catalogue extended; agentKeyDef() shows the agent's name for an entity-id pill via the new layer FilterKeyDefinition.formatVal; useAgentFilter / InventoryAgentFilterChip and every hand-rolled chip row retired; Sessions' period is now the global timeframe; the fleet index's hand-rolled DataMetricBase box and sparkGeo sparks became DataMachines. Surface section: backend /api/surface?<scope>= (agent / host / user / team / model / server / tool / workspace / adapter, none = fleet, own axis omitted; builder lifted out of /api/agent; Rust tests per scope); layer DataSurfaceSection chrome; qdash <SurfaceSection> + lib/surface-rollups.ts (bar metric per axis, house cells, listingTo deep links onto the filtered listings); placed on all seven actor detail pages and the fleet index (replacing five hand-rolled lists and agent detail's inline block; tools detail net new). Design site: DataSurfaceSection demo + registry entry, Filter demo documents formatVal, user-v3 adopts the chrome, stale page-registry note corrected. Layer released as 0.9.15. Verified headless on the Meridian seed with a clean console on every page; npm test 232, cargo test -p qdash 52. Not done: Sessions detail (phase 3), the expand-row compact grid, migrating Findings / Cost onto the global filter; the live :7878 backend must be restarted on the rebuilt binary for /api/surface.
session-details executed (2026-09-19, phase 3 thread 1 of 5): /inventory/sessions/detail replaced in place with the c90 session mock (session-details-prompt.md, -plan.md). Backend: session_detail_value now serves the run's event grain (events, ≤500, typed per event — tool calls pre-joined with decision and result by call_id, message text and tool arguments passed through a new normalize::redact_text so secrets/SSNs render masked exactly as the DLP findings show them) and token_parts; the "may not serve an event log" worry in plan.md was moot — the events table had everything. qdash: lib/session-transcript.ts (vitest 9) turns the grain into the layer TranscriptPanel shape (a turn per user prompt, one tool part per call, policy kept out of the dialogue), raw-event rows, the Surface chip grid, the rail's policy-event count. Page: identity ribbon, Transcript ⇄ Surface, TranscriptPanel with the BYOK disclaimer, Raw events toggle, sticky rail of four DataMachines + labelled fields + Files, "(AI gen summary)" and Account projected; per Mark, everything the old page had that the mock doesn't (Actions, Commands, Egress, Sensitive files, DLP, Models & Tokens) stays under a "More detail" rule. Verified on the seed (Marcus's run with an AWS key in the prompt renders masked; Priya's Cursor run with a denied call); no event-less run exists in the seed, so that branch is covered by tests only. cargo test 54, npm test 241.
kick-off (2026-09-18): Research and plan, no code. Located the real target (qdash UI, not app.qpoint.io), verified layer 0.9.13 is installed there with every component the page needs, mapped the fixture world → /api/* gap (the problem's shape is data, not components), read app.qpoint.io's filter end to end (layouts/global.vue teleport, URL-computed filters, query-string store persistence, lazy requestValSuggestions, useTrafficFilters consumption) and restated it for qdash. Four decisions taken with Mark: rewrite /teams in place · projected badges for unserved denominators · replicate the global filter shell-wide · keep team assignment in the expand row. See plan.md, global-filter-plan.md.
phase 1 + 2 (2026-09-18): Executed plan.md end to end on qcontrol feat/new-pages (twin record: qcontrol/crates/qdash/ui/cycles/c15.teams-users-devices-port/CYCLE.md). Layer: finished the sticky/stuck prototype in filter/index.vue — the component owns pinning (stickyTop prop, _stuck class + stuck emit, bg-surface-sunken backing), filter v2 hex → tokens; released as q-nuxt-layer 0.9.14. qdash: lib/filter-query.ts (URL grammar, 14 specs), lib/filter-keys.ts, composables/useGlobalFilter.ts (singleton, URL as truth, localStorage follow-across-pages, ready gating), components/GlobalFilterBar.vue mounted once in layouts/default.vue; lib/people-rollups.ts (16 specs: person/device/team rows on real /api/* data, PROJECTED constants for the unserved denominators); pages/teams.vue rewritten in place from the design source with projected badges, inline team assignment kept, ?team=/?user= deep links preserved; nav label → "Teams, Users & Devices". Verified headless against live :7878 (sparse) and the Meridian seed (populated): console clean, filter round-trips through the URL, persists across pages, pins on scroll, both themes. Endcap SVGs were already real art — the "checkerboard" fix in global-filter-plan.md §3.4 was moot.
c90 qcontrol Preview
filter alignment pass (2026-09-18): Tuned teams.vue's page-level Filter and per-section FilterTriggerBar placement and the section backgrounds against the mock, iterating with capture checks (public/archive/2026-q3/sweep/c90-filter-align-check*.png, -bg-check.png — scratch shots, not archive entries). Two global layer tweaks made in the same pass while tuning the chrome, shipped in q-nuxt-layer 0.9.13 and visible to every consumer: UxAvatar border weight border-1 → border-2, and UxPageTitle's underline padding pb-1 → pb-3.
user-details-v3 (2026-09-17): New app/pages/qcontrol/user-v3.vue executing the dropped designs/user-details - anotated@2x.png — compact identity header, a DataHistoryGraph "Total Session per Agent" chart paired with clickable headline stats, scroll-anchor tabs (Deployments/Agents/Sessions/Surface, no scroll-spy), QcontrolDeviceAgentsCard.vue device cards (green "Sensor Active" banner + a dense nested Agents table), then the Sessions table reused verbatim from user-v2. Versioned as a new file per the repo's own frozen/working pattern: user-v2.vue frozen as the historical archive, every page's QCONTROL_HOME.person repointed to /qcontrol/user-v3, registry entry added and qcontrol-user-v2's marked superseded. Sections the mock doesn't show (verdict line, chain strip, Models card, Accounts/Surface detail) are deferred, not deleted. Fixture additions for the design's data story: one new fully-covered multi-device person in people.ts and an 'iphone' DeviceKind in world/types.ts (one-line kindLabel addition in the eight exhaustive maps). See user-details-v3-prompt.md / -plan.md.
users-devices-listing (2026-09-17): Combined Teams, Users, and Devices into one page (app/pages/qcontrol/teams.vue) per a newer dropped mock (designs/users-devices-listing-page - anotated@2x.png) — NavJumpPills section switcher, a page-level Filter (team/provider) and per-section FilterTriggerBar replacing the old click-to-filter header rings, DataMachine presets for the coverage stat cards, and a net-new Teams table (the mock's own annotation flagged it missing). Retired the standalone /qcontrol/devices route (sidebar + registry updated); its content is now the page's Devices section. First real (non-demo) consumer of the DataMachine/Filter/FilterTriggerBar layer components. See users-devices-listing-prompt.md / -plan.md.
session-details (2026-09-17): Reworked app/pages/qcontrol/session-v2.vue against designs/session-details@2x.png — a two-column body (reading column + sticky metadata rail) replaces the single full-width column below the receipt. Added sessionTranscript() (app/data/phase1/derived/session-detail.ts) mapping the c77 beat/chapter grain onto the c85 TranscriptPanel family's Transcript/TranscriptTurn/TranscriptMessage/ContentPart shape — chapters become turns, each machinery event inside an action beat becomes its own Ran: … tool-call row. Two decisions made with Mark before building (see session-details-prompt.md): policy violations stay out of the transcript entirely (no policy-speaker concept in TranscriptMessage; they remain in the existing Exceptions box, now linking to a raw-events row instead of an in-transcript beat anchor) and the TranscriptNavigator turn-navigator ships despite echoing the removed "Chapters" concept, since it's a separately-shipped c85 interaction. The rail's Tokens/est. Cost/Tool Calls/Policy Events tiles are DataMachine instances (Cost's $ rides the #left slot since DataMetricValue's unit always renders after the number; Policy Events uses healthModes.ZERO_TOLERANCE); User/Agent/Device/Account/Repo reuse the receipt's existing QcontrolEntityChips, relaid as labelled fields. Files is a new list sourced from the session's file_access events directly, honest to the real event shape (no permission-decision field, so a sensitive read reads "sensitive read" rather than the mock's literal "read not allowed"). First real (non-demo) joint consumer of TranscriptPanel and DataMachine. Verified via SSR curl against all five switcher sessions (including the no-events case) — no runtime errors. See session-details-prompt.md / -plan.md.
session-details, work-in-progress toggle (2026-09-17): Follow-up per Mark's review — the pre-mock verdict line + receipt card (identity header, four stat blocks, Exceptions box) and the Composition notes section moved below the mock-faithful transcript/rail view and collapsed behind a "Show Work In Progress" toggle (showWip, off by default), since the rail and Exceptions box already carry that information for an ordinary reader. The scenario switcher and tab switch stay visible at top (still working/navigation aids, not review-only scaffolding).
session-details, transcript disclaimer (2026-09-17): Follow-up — the BYOK/encryption disclaimer paragraph moved below TranscriptPanel (was above it) and collapsed behind a "Show details" toggle (showTranscriptDetails, off by default), so the transcript itself is the first thing a reader sees under the tab switch.
session-details, default session + switcher relocation (2026-09-17): Follow-up — the page now defaults to Kai's blocked-tool session (ses-kai-04, a local DEFAULT_ID override in session-v2.vue, not a change to derived/session-detail.ts's shared DEFAULT_SESSION_ID, which the other session-detail pages still default from) instead of Marcus's sensitive-file session. The "review a session" scenario switcher moved out of the top working view into the Work In Progress block, alongside the verdict/receipt card it was always scaffolding for.
initial assembly (2026-09-16): Built the chrome and assembled the seven pages. app/components/QcontrolSidebar.vue — a route-aware left nav modeled on the qdash AppSidebar.vue replica, scoped to the three listing links (Teams & Users, Sessions, Devices) with the wordmark on top; app/layouts/qcontrol.vue composes it with the main slot. app/pages/qcontrol/{teams,team,user,sessions,session,devices,device}.vue — five copied/adapted from the best phase1 iteration (teams-v3, sessions, user-detail-v2, session-detail-v2, device-detail-v2; state toggle dropped, cross-links repointed through a local QCONTROL_HOME map rather than the shared routes.ts), team.vue and devices.vue net new. app/data/phase1/derived/devices.ts mirrors derived/sessions.ts's shape (a DeviceRow per world device wrapping coverageGap/deviceHeadline from device-detail.ts, a band, a verdict). app/data/page-registry.ts gained a qcontrol page group and an href override field so registry cards can point at a route outside /pages/<slug> — one qcontrol-preview entry as the flow's front door, then one entry per page. Pages later forked into frozen/working pairs (user.vue/user-v2.vue, session.vue/session-v2.vue, device.vue/device-v2.vue) so manual design iteration has a live target while the assembled original stays as the archive. app/components/QcontrolEntityChip.vue (2026-09-17) extracted the person/device/agent/account/repo chip every qcontrol page repeats.
c89 Teams–Sessions Listings
Backfilled 2026-09-18 from the shipped code — Key Changes were not written as the work happened.
- 2026-09-15 —
derived/sessions.ts. New module mirroringderived/teams.ts's conventions:SessionRow(asessionReceipt(w, s)wrapper with listing-friendly fields flattened on top),sessionRoster(w)(every session in the world's recent sample, ranked violations → runaway cost → clean),sessionsBand(w, rows)(session count against the honest 7-day window, cost with tokens in the sub, the flagged list), andsessionsVerdict(w, rows)(warnonly when a flagged session is a policy violation, elseattention). Flag state is never re-derived — it readsreceipt.attentionfrom session-detail.ts, the one source of truth.sessionsVerdictdeliberately not folded into derived.ts's centralverdictsmap yet.
- 2026-09-15 —
phase1/sessions.vue(net new). The Sessions roster: verdict line, a sessions/cost stat pair labelled "· 7d sample" so it never overstates what's on record (no donut ring — "how many sessions" has no natural denominator), a named "needs a look" list capped at 5 with an overflow note, and aDataTableroster (person, team, agent, started, duration, tokens, dollars, models as un-linked entity chips, outcome pill). Rows link straight to the existing Session detail receipt viaonRow— no per-page expand, since session-detail.vue already owns chapters, transcript, raw events, and the actor web. Composition notes appended per theteams.vueconvention.
- 2026-09-15 —
phase1/teams-v3.vue. Header-treatment iteration onphase1/teams.vue(PRO-20): the "people covered" and "devices sensored"Phase1StatBlockfractions becomeDataDonutChartcoverage rings (same two numbers fromrosterBand, click-to-filter behaviour rides along), plus a named "needs attention" list —band.investigatefirst (holds a seat, zero telemetry), then filled to 5 fromband.uncovered, reusing the roster's own Coverage-column phrasing so header and table never say one fact two ways. "Using AI · 30d" and "installs with an owner" stay plain stat blocks. Question, verdict, team strip, roster, and expand untouched.
- 2026-09-15 — Registry.
teams-v3(teams group) andsessions(sessions group) entries inapp/data/page-registry.ts, bothphase: 'phase1'.
c91 DataMachine
- 2026-09-17 — Built
q-nuxt-layer/components/data/machine/index.vue(DataMachine): title, full-width hairline, optional description, and provenance row wrapped aroundDataMetricValue/DataMetricTrend/DataMetricSpark, with#left/#rightslots for consumer-composed content. No changes needed to the reused primitives —DataMetricValue's existingunithandling already coversbytes/duration/ms/%/custom passthrough cleanly.
- 2026-09-17 — Documented on the design site: new
data-machineregistry group +DataMachineentry,_demos/DataMachine.vuewith the two reference presets ("User Coverage", "Devices Sensored") plus empty/minimal/typical/overflow/error scenarios and a unit-passthrough spot-check, visual-index placement on/components. Also fixed a stale leftover from the c28 data-metric rename: thedata-metricgroup's registry label and/componentssection header both still read "Data Machine" — renamed to "Data Metric" so the new realDataMachinecomponent doesn't collide with it.
- 2026-09-17 — Wrote the base + curated-presets pattern into
component_philosophy.md(Principle 4 + Design Lineage) anddesign_methodology.md(Decomposition and Modularity), citing qmap'sQMap/QMapDataflow/QMapPolicyViolationas the precedent andDataMachineas the new cross-project instance.
- 2026-09-17 — Swapped the demo's hand-rolled, too-smooth sparkline arrays for the existing
app/data/sparklines.tsfixtures (sparkNoisefor generic examples,sparkUpfor the positive-delta "Devices Sensored" preset) — real telemetry is jagged, not a clean ramp.
- 2026-09-17 — Added a
UxCopyBtn(icon-only, top-right corner) to each preset card in the demo, copying a self-contained Vue template snippet of that exact preset — makes the "copy this, don't hand-assemble" pattern literal instead of just stated.
- 2026-09-17 — Fixed a real bug in
DataMetricSparksurfaced by using it unfilled:svgMachine.sparkChartalways injected baseline anchor points ((0,-10)/(width+10,-1)) meant to close a filled polygon; withfillColor="none"those anchors render as a visible drop at the start/end of the stroked line. Added aclosePathparam (q-nuxt-layer/utils/svg.js) and hadDataMetricSparkpassfillColor !== 'none', so line-only sparklines render clean. Also fixes the same latent artifact inSecurityPolicyCard's three sparklines (verified, no regression).SparkChartOlduntouched — deprecated, out of scope.
- 2026-09-17 — Replaced the static
sparkNoise/sparkUpfixtures with a newgenerateVelocitySpark()helper inapp/data/sparklines.ts— a damped random walk (velocity carries forward instead of each point being independent) so the line moves in short organic runs like real traffic, with an optionaldriftfor a biased trend (used for the positive-delta "Devices Sensored" preset).
- 2026-09-17 — Added optional
slotGapprop (Number, px) — extra spacing between#left/#rightslot content and the main value/delta group, on top of the row's default gap. Implemented as conditional margin onv-if="$slots.left/right"wrappers so it costs nothing when a slot is unused. Demoed with a dedicated "Slot Gap" comparison (default vs.slotGap="24") and documented in the registry.
c88 Global Filter
Phase 2 formalized as a site-wide bucket (2026-09-17): The design site's own /pages index had a single "Phase 2" card pointing at c85's unrelated phase2-chat-transcript.vue — a naming collision with this cycle's own "Phase 2 redesign" work. Resolved by relocating: c85's chat-transcript moved into app/pages/pages/phase1/chat-transcript.vue (registered phase: 'phase1', sessions group — it was always phase1-world-adjacent, not a v2 redesign), and this cycle's lab page moved from the flat phase2-global-filter.vue to app/pages/pages/phase2/global-filter.vue, the first page under a proper phase2 bucket. Added 'phase2' to PagePhase, a pageHref branch (/pages/phase2/<slug>), a phase2 pageGroup, and a new app/pages/pages/phase2/index.vue landing page (mirrors phase1/index.vue's tone, lists every phase: 'phase2' registry entry — no Linear-tracked plan needed since Phase 2 has none). /pages' Phase 2 card now points at /pages/phase2 and describes it as "v2 redesigns of components already extracted in Phase 1" rather than naming chat-transcript.
Phase 2 redesign (2026-09-16): Implemented v2 segmented-bar Filter and supporting components per mockup set: Phase-1 components moved into q-nuxt-layer/components/filter/old/ namespace (auto-registered as FilterOld*, FilterOldDatasetViewSelector) with internal cross-references rewritten; new v2 Filter rebuilt as segmented control (empty "Add Filter" pill, populated state as continuous bar with ADD trigger, two-row FilterItem pills, Config button); new FilterItem with italic key + bold value split across two rows, replaced "!=" text with slash-circle negation glyph; FilterInlineEdit restyled into two-row chrome with same typeahead behavior; FilterDatasetViewSelector built unchanged under live name (unchanged component); new FilterTriggerBar component (contextual quick-filter bar with optional key-select dropdown + single-select value pills). Reused existing q-nuxt-layer assets for icons (gear.svg for Config, no.svg for negation glyph—no new icon assets needed). Updated app/data/component-registry.ts: new filter-old group (id, label "Filter (Legacy)", description), v2 entries added for Filter/FilterItem/FilterInlineEdit/FilterTriggerBar under filter group, Phase-1 entries renamed to FilterOld* under filter-old group. Created demo files: 8 Phase-1 demos renamed to FilterOld*.vue, FilterDatasetViewSelector.vue copied to FilterOldDatasetViewSelector.vue, new demos for Filter.vue/FilterItem.vue/FilterInlineEdit.vue/FilterTriggerBar.vue. Rewrote lab page phase2-global-filter.vue demonstrating v2 Filter alongside FilterTriggerBar wired to same shared keyValFilters ref, proving two-way sync pattern. Left for future: v2 redesign of FilterManage modal (Config still opens FilterOldManage), sticky positioning behavior (strategy recommendations captured; consumer-owned implementation). Verified all new/updated pages via local dev server — all render 200 with no console/icon-resolution errors, screengrabs captured to public/cycles/c88.global-filter/.
Phase 1 extraction (2026-09-15): All 8 components extracted into q-nuxt-layer/components/filter/: Filter, FilterItem, FilterInlineEdit, FilterManage, FilterEmbed, FilterTimeframe, FilterManageItem, FilterDatasetViewSelector — plus q-nuxt-layer/utils/filter.ts for filter-shape constants/types. During extraction, discovered FilterTimeframe and FilterManage embedded business logic (Pinia store, route/org composables) not visible from names alone; decoupled into props/emits, business logic left local. DatasetViewSelector similarly decoupled. TagAutocomplete.vue found to be dead code (hardcoded dummy component) and excluded rather than moved. All 8 documented on the design site with new filter group in component-registry.ts, demo files under app/pages/components/_demos/, live at /components/filter/*. Built v2 lab workspace page app/pages/pages/phase2-global-filter.vue (unregistered, composes real Phase-1 components). Verified via local dev server — all new pages render without console warnings/errors. Decisions recorded in plan.md.
c85 Chat Transcript
- 2026-09-17: Relocated the demo page from
app/pages/pages/phase2-chat-transcript.vuetoapp/pages/pages/phase1/chat-transcript.vue(registeredphase: 'phase1',sessionsgroup) — the "Phase 2" label this cycle coined for it collided with c88's later, unrelated "Phase 2 redesign" work once that cycle needed the name for real. Chat Transcript was always phase1-world-adjacent (a layer component demo, not a v2 redesign of something already shipped), so it moved rather than keeping the name./pages' "Phase 2" card now points at c88's/pages/phase2instead. No component or fixture code changed, only the page's location and back-link.
Captures
17 screengrabs taken this week, 11 of a subject with an evolution history.
DataMachine — the base-component + curated-preset pattern qmap arrived at, now a named layer idea

The qcontrol preview — seven page mocks behind a left nav and app chrome that mimics the real shell

The header idiom: a coverage ring where a real denominator exists, a named attention list instead of abstract counts


Filter v2 — a display-only segmented bar with negation, plus FilterTriggerBar for contextual quick-filtering








The transcript/* namespace — c77's chat-grammar candidate promoted to five reusable layer components
DataHistoryGraph — unit-scaled pictogram columns that fall back to continuous bars past 40 columns
Releases & deploys
Layer releases 8
Design-site deploys 0
No deploys recorded this week.
Note: Deploys come from this checkout's deploy-branch reflog, which starts 2026-10-16.



