← Cycles
Design · c103

qdash TODOs — 2026-09-30 review

archivedcreated 2026-10-01· last activity 2026-10-01· mode: evolution, with alchemy for the trend exploration page

Execute Mark's 2026-09-30 product-review notes (nav restructure, Activity, Teams cost + trend exploration, Users attention) through `/todos`

New in this cycle: Now + Trend — Variations: the Teams page as Part A shipped it (§0) on a dated seed snapshot, then 22 trend forms + Activity's history row, roll-downs, table cells, breakdown over time, toggles, a degradation lab with auto-checks, and a keep / maybe / kill ledger

Why

The 2026-09-30 review restructured the left nav around Monitor / Govern + Secure / Admin and asked, across pages, how to show trend beside "now". c102 is still executing the previous sheet, so this one gets its own cycle.

Objective

  1. Ingest the 2026-09-30 block — first sheet read under the formalized ignore grammar (BEGIN cutoff, #skip, CANCELED).
  2. One plan per page under todos-2026-09-30/, decisions with defaults.
  3. Execute serially, preview → executor → review, after c102 finishes.

Scope

  • Fable app: AppSidebar.vue, Sessions/Activity, Teams, Users pages and their helpers.
  • Design site: a trend-exploration page in c100's Phase 3 collection (plan 03 Part B).
  • Skill: /todos grammar §Ignoring.
  • Out: everything above Begin here; the !SKIP ME! DataMachines item; Govern/Secure (Later).

Key Changes

todos-2026-09-30 planned (2026-10-01): journal 2026_09_30 → 4 plans under todos-2026-09-30/ (01-nav, 02-sessions → Activity, 03-teams incl. design-site trend exploration, 04-users attention component); no sweep (General ignored). /todos grammar gains §Ignoring — CANCELED (item), #skip (subtree), BEGIN (cutoff), with !SKIP ME! read as an alias; README template gains ## Ignored.

todos plan 01 executed (2026-10-01): Nav (todos-2026-09-30/01-nav.md; no twin record) — on the c99 Fable app (qcontrol-c99-fable, c99/claude-code-fable-5.1, :3032 → seed :7882). Rail (shell/AppSidebar.vue NAV): MONITOR Overview · Inventory · Organization · Cost Center · Activity / GOVERN + SECURE Policies · Compliance · Findings · Reports / ADMIN Integrations · Dashboard Settings · Account (dashed pending ? UxAvatar, no viewer identity served, 01.9); WHERE ARE WE, THE FLEET, WHAT GOOD LOOKS LIKE, ACT and PROVE retired. Mark, 2026-10-01: Monitor order Overview · Inventory · Organization · Cost Center · Activity (EXP items trail Activity). Mark, 2026-10-01: Cost Center is a top-level MONITOR item on the existing /cost vCost page, out of EXP, no move, no placeholder (01.4); Compliance un-EXP'd too. Mark, 2026-10-01: off-tree pages ride Experimental (01.3) — Experimental on adds Live Activity · Performance · Harness Identity under MONITOR and Security Posture · Data Protection · Campaigns · Alerts under GOVERN + SECURE. Routes (M.2, 01.1 item-level): 12 git mvs — /fleet→/overview (title Overview, 01.12), /team→/organization (landing + six crumbs read Organization, 01.6), /team/{teams,users,endpoints}→/organization/…, /team/sessions→/activity (crumbs/title left to plan 02, so the page still reads Team / Sessions), /controls→/policies (page keeps "Control Center", 01.7); the sweep rewrote 126 link lines in 62 files (re-grep → 0 outside the redirect table and its specs); Organization's submenu is Teams · Users · Endpoints, Activity is a plain item (Working list label Activity). New DesignInProgress landings /reports, /settings, /account (M.1, copy as drafted, 01.8). Redirects: c92's team-routes.global.ts existed only in the ui app — ported into the Fable app (app/middleware/team-routes.global.ts, table in lib/moved-routes.ts), c92's four entries re-pointed at the final paths plus /team/*, /fleet, /controls; verified in the pane: /team/sessions/detail?runId= → /activity/detail?runId=, /team/teams/detail?team=Data → /organization/teams/detail?team=Data, /users/detail?user= → /organization/users/detail?user=, /fleet → /overview, /controls/detail?policyId=p5 → /policies/detail?policyId=p5, hash kept; Back after a full-load redirect returns to the prior page. What survives of c92 team-nav's ruling: the equipment-vs-who split — Inventory = what's in the fleet (equipment), Organization (was Team) = who and from where (Teams, Users, Endpoints); Sessions left the who-group for a third axis, what happened (Activity); Fleet Overview still leads, as MONITOR · Overview; DesignInProgress landings, WorkingNav and lib/working-nav.ts reused; it revises only the /team/* paths. Decisions 01.2, 01.5–01.13 taken on defaults (01.13: c100's LandingMockSidebar untouched — flag for its next mock thread). npm test 537 → 542 (+1 working-nav, +4 moved-routes) + bible-check clean. Design side: todos-pages.json routes moved (7 ids, _readme notes :3030 no longer serves them) + new nav id; reference/pages.md rows moved, new Overview row, Sessions row aliased Activity. After shots todos-2026-09-30/after/{nav,teams,sessions}.png (+ -full); published public/cycles/design/c103-todos0930-{teams,sessions}-{before,after}.png (subjects page/teams, page/sessions; nav has no baseline). The c103 README index table still lists /team/* routes (coordinator's file). Committed c6843703 on c99/claude-code-fable-5.1 (local, unpushed).

todos plan 02 executed (2026-10-01): Activity, was Sessions (todos-2026-09-30/02-sessions.md; no twin record) — on the c99 Fable app (:3032 → seed :7882), files at plan 01's moved paths (app/pages/activity/{index,detail}.vue). Header: title Activity; no crumb on the listing (the :breadcrumb prop dropped, Overview's idiom, rather than the plan's empty array); Session detail crumb Activity /, tab "Claude Code · 6eb07b16 · Activity · qdash" (02.7). Table: DataTable title="Recent Work", the Status strip on the same row (02.8); Started already there — M.3 satisfied, no edit. Band (SessionsBand.vue, 4-up grid-cols-2 md:grid-cols-4): Sessions · Harness Installs · Tokens · Cost (02.4); Flagged card and the page's flaggedTo removed, the Flagged quick filter and the history's clean · flagged lanes kept (02.6); Tokens' device is now the top-3 tokens share and Cost (02.5, $, links /cost) carries the cost share. Trends (02.1): lib Spark in each card's #title-right, per-day over the loaded rows via dailyTotals (moved verbatim from detail-bands.ts into actor-bands.ts with its doc comment, re-exported; 02.2 — rows, not /api/costs.series); em-dash on 24h. Mark, 2026-09-30: "add historical/trend data to all of these" — reverses c92 day-series §D's sparkless band cards. Harness Installs (02.3): distinct endpoint × harness (host|system_id) among the filtered rows, device by harness, "N of M scanned" from useInstallationList(). Its trend is projected (M.4): distinct installs with a session each day, badged from the new ledger entry PROJECTED.session.installsPerDay (contract GET /api/history?axis=agent — c92 day-series §B, still 404), retired when §B lands. Seed values, Past 7 days, match the plan: Sessions 52 (1 running · 51 completed · 0 failed, ▲ 11); Harness Installs 26 (Claude Code 11 · Codex CLI 8 · Cursor CLI 4 · Other 3; "26 of 39 scanned installs ran a session · 1 session without a harness id"; per day 7·7·2·2·10·11·11, ▲ 11, PROJECTED); Tokens 24m (tomas · Codex CLI 1.7M, elena · Claude Code 1.2M / 1.1M; top 3 = 17 %; ▲ 6.65M); Cost $331 ($35.69 / $23.63 / $21.65; top 3 = 24 %; ▲ $66.07 red). Checked in the pane: 24h 11 sessions, all four trends em-dash, badge kept; 30d 200 rows, Sessions provenance "· newest 200 — the trend stops at the cap", installs 34 of 39 · 11 without a harness id; status=running → every headline 1; the Cost card lands on /cost. At 1440 px the Harness Installs spark + badge wrap under its title (the documented #title-right narrow-card behavior). Decisions 02.1–02.8 on defaults. npm test 542 → 543 (+1 installs/trends it; the flagged expects dropped) + bible-check clean. After shot todos-2026-09-30/after/sessions.png (+ -full); published public/cycles/design/c103-todos0930-sessions-after-p02.png (+ -full), subject page/sessions set by hand to continue plan 01's pair (capture now derives page/activity from the route); no before-p02 — plan 01's after is this plan's before (c92 -p07/-p08 precedent). Committed 5f436e12 on c99/claude-code-fable-5.1 (local, unpushed).

Activity usage history (2026-10-01, ad-hoc thread after plan 02 — activity-usage-prompt.md): on the c99 Fable app (:3032), /activity. Opened as "c104", filed here at Mark's request. Reverses plan 02's trends: Mark, 2026-10-01, "remove the sparklines, instead add providers, models, harnesses to the second row showing historical data for each (uses per day)". Spark left all four band cards, so the projected installs trend + badge went with it and the PROJECTED.session.installsPerDay ledger entry retired. History row (SessionsHistory.vue): Sessions started (clean · flagged) · Providers · Models · Harnesses, each "Sessions per day" (sessions using the item), top 3 only (Mark switched from 5; no Other lane), all on byDay's columns so they share one day axis and filter with the table; provenance "top N of M · X % of sessions"; "per day" moved from titles to descriptions. New lib usageByDay + sessionsBand.usage (actor-bands.ts). Providers is projected: list rows carry no provider (only ?run= detail serves account_provider), so inferredProvider reads harness then model family then harness vendor (Cursor CLI → Cursor, matching /api/providers' cursor routes); badged from the new ledger entry PROJECTED.session.providerOfSession (contract: provider on /api/sessions rows). Layout: the history row is a DataMachineGroup on the band's grid (grid-cols-2 gap-x-10 md:grid-cols-4), so each chart sits under its band card; past 7 day-columns a chart spans the row (a 30d chart is ~890 px). Band → history gap 60 px (mb-[60px]; mb-15 isn't on the scale). History bodies bottom-aligned (scoped CSS, a layer candidate). Hover: a day on any chart opens the layer's DataHoverBreakdown (v0.9.23) — date title, the day's sessions as metric, rows = swatch · full label · count, plus a dim "other" row on the top-3 charts so rows sum to the day. Layer changes it rides on (q-nuxt-layer working tree, uncommitted, under evaluation; Mark may roll back): DataMachineGroup alignFooters (opt-in 5-row cards, provenance on its own row so hairlines align with no reserved space; the default keeps the documented "a tall device never pushes a sibling's footer"); DataHistoryGraph legendWrap (false = one line, out of the chart's width, labels ellipsize with the full name on hover, tighter 6 / 12 px spacing, 12 px chart → legend instead of 32); DataHistoryGraph @hover / @leave (column index, date, values, resolved series, pointer; a listener turns the native block titles off); the hovered day gets a highlight (bg-surface-sunken-subtle = grey-50 in light + 1 px stroke-strong border, 4 px past the sides, 10 px above, 6 px below, no layout shift) and its label turns text-content. Activity opts into all of them; the app-side legend / footer CSS they replaced is gone. A lane-packing change (no gap for a zero series) was made and reverted at Mark's request. Gotcha: Tailwind didn't extract classes inside a template-literal interpolation (plain string literals now), and the design site needed a nuxt.config.ts touch to rescan the layer. npm test 543 → 546 + clean. Not yet done: design-site docs for the three layer props, a layer release (with c104's Table.vue, as 0.9.24), and the Fable commit (SessionsBand.vue, SessionsHistory.vue, actor-bands.ts + test, projected/session.ts, activity/index.vue — uncommitted).

todos plan 03 executed (2026-10-01): Teams, Part A (todos-2026-09-30/03-teams.md §A–§C; no twin record; Part B, the design-site "Now + Trend — Variations" page, follows in its own line) — on the c99 Fable app (:3032 → seed :7882), app/pages/organization/teams/index.vue → TeamsListing.vue. Roll-down (§A): teamRows sorts by spend, then tokens, unassigned last (03.2; Frontend now above Data at 7d); the Spend tile's ranked bars name up to six teams + rest (03.3), and its provenance drops "top N = …%" unless a rest exists ("top 4 = 100 %" said nothing). Trend (§B, served, 03.1): new useTeamCostSeries (composables/fleet/costs.ts) — one /api/costs?team=&period= per team under the pill (03.8) through a fan-out Transport under useResult (poll, single-flight, gf.ready kept); team names sorted into the query key so a spend reorder doesn't refetch; 12-team ceiling (03.5): above it no fetch, every cell an em-dash titled "per-team history needs /api/history". New lib team-cost-trend.ts: teamCostTrend zero-fills each team on the fleet series' day axis (no usage = $0; a failed fetch = null → em-dash), trendTotal sums the shown teams per day, sparkOf → [{ count }]. The table gains a Spend trend column after Spend (qdash Spark, downIsGood; the column picker can hide it); the Spend tile carries a spark when the window has ≥ 2 days. Two deviations from the plan: the tile spark is the shown teams' per-day sum, not the raw fleet series — under a quick filter the tile reads $63 (Data) and a fleet line under it would draw other rows than the number (unfiltered they are the same line: Σ teams = total_cost at 7d); and Spark's last-day label goes through a local fmtDay ($0 for a zero day), because fmtC(0) renders "—" and Data at 7d read "▼ —", Spark's own "no history" glyph. Seed values, Past 7 days, match the plan: Spend $331, rows Payments $156.22 · Frontend $69.86 · Data $63.06 · Platform $42.30 in tile and table, provenance "24.0M tokens", trend cells ▲ $39.86 · ▲ $21.65 · ▼ $0 · ▼ $4.56. Checked headless (Playwright on :3032, no console errors): 24h → every trend cell an em-dash, no tile spark (one-day axis); 30d → 29-day lines, Spend 909.21 · 403.76 · 324.48 · 288.75 (= by_team); column picker hides Spend trend; quick filter Data → one row, its line identical to the unfiltered one, the tile spark redrawn on Data alone. Data: ?period=7d&team=Payments → 156.2176…, 5 days; team=unassigned → 0, 0. §C (window-over-window delta, breakdown over time) left to Part B (03.4, 03.6). Decisions 03.1–03.5, 03.8 on defaults. LATER: useTeamCostSeries calls useCosts() for its day axis beside usePeople's own, so /api/costs?period= polls twice on this page (exposing usePeople's costs would fold them); above the ceiling the tile loses its spark too. npm test 546 → 550 (+1 people-rollups it, +3 team-cost-trend; 546 = 543 committed + 3 from the uncommitted Activity usage-history thread) + bible-check clean. After shot todos-2026-09-30/after/teams.png (+ -full, replacing plan 01's); published public/cycles/design/c103-todos0930-teams-after-p03.png (+ -full), subject page/teams set by hand to continue plan 01's pair; no before-p03 — plan 01's after is this plan's before. Committed fc0b32c8 on c99/claude-code-fable-5.1 (local, unpushed).

todos plan 03 corrected (2026-10-01, Mark): "move the sparkline to a slot right beneath the val" — no such slot existed, so q-nuxt-layer DataMachine gains #value-below (inside the value group, under the number + delta; w-0 min-w-full so it takes the value's width and never widens it). The Spend tile's summed daily spark moves there from :spark (card foot). "use the existing DataMetricTrend for our trends in the table, lose the red/green" + "make the sparkline fill the whole space, pushing trend to the end" → each Spend trend cell is a full-width neutral DataMetricSpark + DataMetricTrend (later half vs earlier half of the window, %, new trendChangePct() in lib/team-cost-trend.ts, +1 test), em-dash below two days or with no earlier spend; qdash Spark no longer used here. Layer fix found on the way: DataMetricTrend hid any change ≥ 1,000 (it tested the abbreviated string, "2k" > 0) — now tests the number. App fe86f40d (local); tests 553 + bible-check clean. Layer edits on disk only (machine/index.vue shares c104's uncommitted alignFooters work; metric/Trend.vue) — to ship with c104's 0.9.24.

todos plan 03 follow-up (2026-10-01, after close): plan 03A's LATER "/api/costs?period= polled twice" fixed — useTeamCostSeries opened its own useCosts() for the day axis beside usePeople's (same shell-period URL; useResult's single-flight is per instance). usePeople now returns costDays and the Teams page passes it in. Measured headless on :3032/organization/teams?period=7d: one ?period=7d per 3 s poll, was two. npm test 553 (unchanged) + bible-check clean. Fable d77bcc9e (local). Not fixed (pre-existing, harmless): the per-team fan-out runs twice at startup (when and url watchers both fire).

gap-aware (2026-10-01, after close — gap-aware-prompt.md · gap-aware-plan.md): plan 03B's LATER, "a day not collected draws as zero", fixed in the layer and released as q-nuxt-layer 0.9.25 (b1c57f7, tag 4c298c0, publish workflow green; migration notes in the layer CLAUDE.md). DataMetricSpark (via svgMachine.sparkChart): { count: null } or a bare null keeps its x slot and breaks the line; a fill closes each run to the baseline with the stroke on the top edge only; a lone known day is a dot. Gap-free data is byte-identical (six-case sparkChart snapshot, before = after). DataHistoryGraph: a point with values: null or missing: true keeps its slot and label, draws no lanes, a hatched area and a dashed baseline; hover reports missing; values: null no longer throws; a per-series null still reads as 0. qdash Spark (Fable a011f775): lifts the pen at a null day and dots a lone day — the design replica's fix ported back; Campaigns' three sparks unchanged; npm test 553 + bible-check clean. Demos: _demos/DataMetricSpark.vue (gaps line, gaps area, first run, nothing collected) and _demos/DataHistoryGraph.vue + history-graph-fixtures.ts (gappedWeekFixture: Wed values: null, Sat missing, Fri a real zero; notCollectedFixture), checked headless on :3000 against the installed 0.9.25. The components were first checked on :3032 (sibling layer, live) through a scratch page, deleted afterwards. Lockfiles to 0.9.25: design c251200d, bob-wire 49a348c, app.qpoint.io 903338d, www e2b77ce, qflow 96a4b62, qcontrol ui d52ff77b, Fable c4d0e007. Not changed: the trend page's design-local workarounds (its own gap-breaking drawer for F3/F4) stay until it's integrated.

todos plan 03 Part B executed (2026-10-01): Now + Trend — Variations, the design-site exploration page (todos-2026-09-30/03-teams.md Part B; decisions 03.6–03.9 on defaults; thread docs in c100 — prompt · plan, record line "trend-variations" in c100 CYCLE.md). Page /pages/phase3/landing-trend-variations (registry after Lever Strip, overview, phase 3; the Phase 3 index lists it). Fixture app/data/landing-trend.ts: buildTrendWorld derives a deterministic daily series from c100's people (tokens30 / 30 × weekday × volatility × adoption ramp, zero before first seen and through an idle tail), split by harness, costed per harness, then harness → provider → model through landing-estate ROUTES / MODELS (a model exists from its firstSeenDays, so the mix shifts); every rollup is a sum, so every drawing cross-foots. Knobs: world (seed / 20 / 200 / 1,500) · state (first-run = last 2 days known) · gaps (0 / 10 / 40 % of days not collected → null, fleet-level) · volatility (steady / bursty / spike). Seed snapshot (03.9) app/data/landing-trend-seed.ts: Meridian from :7882, captured 2026-10-01 — 81 served days (2026-04-03 → 2026-07-01), cost + tokens per team per day, served totals per period (7d fleet $331.44 · Payments $156.22 · Frontend $69.86 · Data $63.06 · Platform $42.30; 30d $1,926.20; all $4,211.15), window compositions, /api/teams roster; the 24h / 7d / 30d series are exact slices of all and Σ captured days = served at every period. Parts (app/components/landing/trend/, replicate-first): LandingTrendSpark (qdash Spark replica — breaks its line at a null day, which qdash doesn't), LandingTrendDraw, LandingTrendForm, LandingTrendRollDown. Sections: §0 Part A as shipped (Spend $331 rolled down, tile spark = Σ shown teams, Spend trend column; em-dash at 24h) · §1 F0–F21 + AH, Activity's history row (2026-10-01) · §2 R1–R7 roll-downs in the DataMachine seat at S2 / S3 / S4 · §3 seven cell forms, merged vs separate, three sizes · §4 F14–F18, F20, AH with the grouping lever · §5 F19 per card / per page + title switchers (#title metric, #title-right trend window, reserve, fadeKey) · §6 lab (forms × S1–S6 or free W × H, D / M / R checks) · §7 ledger (keep 10 · maybe 11 · kill 2 — F13 horizon, F21 delta-only), served-today column, live cross-foot table. Trend window follows the pill with a fixed-90d lever (03.8); on the seed, breakdown over time and sessions per day say "needs /api/history" (03.6). AH uses the uncommitted q-nuxt-layer DataHistoryGraph legendWrap + @hover / @leave (live layer on :3000; a rollback quiets its hover, nothing else depends on them). Checked headless at 1440 against :3000: 4 worlds × 5 windows + 6 lever states — no console or hydration errors, cross-foot clean on every state; fixture harness (worlds × gaps × states × windows × metrics × groupings) 0 failures; the fixture type-checks strict. No design lint / test scripts exist. Captures (program scripts/shot-programs/c103-trend.json, thread "trend"): public/cycles/design/c103-trend{,-full,-rolldowns,-models,-gaps,-gaps-full,-lab,-lab-full,-24h}.png. Committed 8c2372c0. LATER: a layer null path for DataMetricSpark and DataHistoryGraph (a not-collected day draws as zero there); the qdash Spark gap fix ported back; F1's DataMetricTrend prints "+35.98", not "$35.98".

todos plan 04 executed (2026-10-01): Users (todos-2026-09-30/04-users.md; no twin record) — on the c99 Fable app (:3032 → seed :7882), app/pages/organization/users/index.vue (plan 01's path). Supersedes c102 plan 14 (04.1) — Mark, 2026-10-01: "I ignored this in the previous plan, ours supersedes"; c102 14 §B's group-by-reason card is not built (04.4 keeps it as a later select option), and c102 plan 14 §A's Devices → Endpoints rename landed here: the roster column (cols.devices.name, width 100 px; key, shown('devices') and sort unchanged), its tooltip (no endpoints), the expand row's identity-grid label and the page subtitle. Lib (§A): PersonRow.reasons (attention grouped by kind, counted, worst first; a lone item keeps its verb-link) and PersonRow.severity; ATTENTION_SEVERITY (critical 4 · no identity 3 · detected-only 2 · elevated 1), attentionReasons, attentionSubjects (every row with attention — Elevated now counts, 04.3 — worst first, then issues, then spend), ATTENTION_SORTS (severity · spend · sessions · issues · name, 04.5); usePeople returns attention (from all rows, not the Team pill, 04.6) and registers a new attention filter key (FILTER_KEYS.attention: any + every reason label). Staged components (04.7, R3): components/lib/AttentionList.vue (ranked by a parent-owned key, stable ties, cap 5, the active sort's value right-aligned when it has a fmt, #footer {total, shown} / #empty) and AttentionReasons.vue (inline tone-labelled reasons, ×N counts, NuxtLink titled with the verb when to); both import nothing from lib/ (their item types are their own); two LAYER-CANDIDATES.md rows (DataAttentionList, UxAttentionReasons). Band (§B): the card is now Users needing attention (04.8) with a #title-right UxSelectInline by severity ▾; the old first-reason-wins builder and its r.detected read are gone (the band reads no field). Seed, Past 7 days, by severity: No identity (muted) no identity · priya detected-only · wei detected-only · felix detected-only · chen elevated posture (amber); by spend: priya $57.05 · wei $37.65 · chen $17.97 · felix $13.39 · No identity —; by sessions: wei 6 · priya 5 · felix 5 · chen 2 · No identity 1; by issues: all 1 (severity order kept); by name: chen · felix · No identity · priya · wei. Under the rows "Show in the roster ↓" (5 ≤ cap, so not "Show all N"), then seat holders PROJECTED "1 holds a seat, zero telemetry — investigate" — the badge sits beside its label, but at 1440 px the sentence wraps to a second line (the plan expected one). Expanded view = the roster (§C–§D, 04.2): an Attention column after Coverage (AttentionReasons, — when clean), sortable by severity; "Show in the roster ↓" sets attention=any and switches the strip's key to Attention (any · detected-only · elevated posture · no identity). Checked in the pane: clicking wei opens wei's roster row (identity grid reads Endpoints); wei's detected-only lands on /organization/endpoints/detail?host=wei-tower; Show in the roster → ?attention=any, 5 rows (wei · felix · priya · chen · No identity); elevated posture → chen alone; Attention ↓ → No identity, wei · felix · priya (detected-only ties keep the roster's own order, not spend), chen; All → 15 people; no console errors. Deviations: ATTENTION_SORTS is a typed array rather than as const (so it feeds AttentionList's sorts prop without a readonly cast); the new spec block builds its own rows/ann (the plan's snippet borrowed the personRows describe's locals). Decisions 04.1–04.8 on defaults. npm test 550 → 552 (+2 its; 550 = 547 committed + 3 from the uncommitted Activity usage-history thread) + bible-check clean; both Verify greps empty. After shot todos-2026-09-30/after/users.png (+ -full); published public/cycles/design/c103-todos0930-users-{before,after}.png (+ -full), subject page/users set by hand (publish left it unset). Committed c8a15b16 on c99/claude-code-fable-5.1 (local, unpushed).

todos plan 04 corrected (2026-10-01, Mark): "let's add a avatar to the user component" — AttentionList rows (Users needing attention) lead with a small UxAvatar beside the name, as in the roster; the No identity bucket gets a pending dashed "?" avatar. Rows align centre instead of baseline. npm test 552, delta 0 + bible-check clean. Committed e96e1b63 on c99/claude-code-fable-5.1 (local, unpushed).

todos plan 04 corrected (2026-10-01, Mark): "either add descriptions to these two data machines, or remove the description from activity" → added: Coverage "Known users the sensor has seen", Users needing attention "Identity, coverage or posture gaps" (Activity keeps "Users using AI agents"), so the band's value rows align. "a bit smaller avatar" → AttentionList avatars 36 → 24 px (!h-6 !w-6; UxAvatar has no smaller size). Committed 1a306a51. Then "let's remove" the seat-holders line → the PROJECTED "1 holds a seat, zero telemetry — investigate" footer is out of the attention card (the ledger value stays); committed on c99/claude-code-fable-5.1 (local). Tests 552 + bible-check clean throughout.

q-nuxt-layer 0.9.24 released (2026-10-01, Mark: "release the layer, commit work"): layer 845959e (c103: DataMachine #value-below, DataMachineGroup alignFooters / rowGap, DataHistoryGraph hover + legendWrap, legend wrap, the DataMetricTrend ≥ 1k fix) on top of c104's sticky band (useStickyBand, UxStickyBand, UxOverflowX, DataTable pinned thead), tagged v0.9.24, published by CI. Lockfiles refreshed and committed in design, bob-wire, app.qpoint.io, www.qpoint.io, qflow, qcontrol ui/ and the Fable app (which update-consumers.sh doesn't list — it was on 0.9.22). The Activity usage-history code was committed on the Fable branch as 92336c1a. Tests 553 + bible-check clean. Final shots of nav, Activity, Teams and Users published as c103-todos0930-*-final.png.

Outcome

Closed 2026-10-01 (Mark). Mark's 2026-09-30 review ran through /todos on the c99 Fable-track app, all in one day: 4 of 4 page plans executed (no sweep: ## General sat above the Begin here cutoff), each with a preview Mark corrected and a review after, plus three follow-ups.

  • Nav (01). The rail is now MONITOR (Overview · Inventory · Organization · Cost Center · Activity, Mark's mid-run reorder) / GOVERN + SECURE / ADMIN. Routes moved to match (M.2: /overview, /activity, /organization/*, /policies) behind a ported redirect middleware. c92 team-nav's split survives as Inventory (equipment) vs Organization (who and from where).
  • Activity (02). Renamed from Sessions, with Recent Work, Harness Installs and Cost tiles. Plan 02's per-card sparklines were then reversed by the ad-hoc Activity usage-history thread: providers · models · harnesses per day, with hover breakdowns.
  • Teams (03). Every team appears in the Spend tile. A served per-team Spend trend column renders as a neutral full-width spark + DataMetricTrend with no red/green, and the tile's spark sits under the value via the new #value-below slot. Part B, "Now + Trend — Variations", joined c100's Phase 3 collection.
  • Users (04). A sortable AttentionList with avatars and descriptions; the roster is its expanded view (Attention column + filter). c102 plan 14's Devices → Endpoints rename landed here, and the seat-holders line was removed.
  • Layer. 0.9.24 shipped every layer change the sheet needed, alongside c104.
  • Process. /todos gained §Ignoring (CANCELED · #skip · BEGIN, with !SKIP ME! read as an alias) and §Indentation in code blocks, after a tab/space mix misread Cost Center as an Inventory child. Lineage to c92 is noted, not copied.

Lessons

  • Several sessions ran this sheet at once (Part A and Part B elsewhere, the Activity thread elsewhere). Check git log and file mtimes before launching an executor; one Part B executor stood down for exactly that.
  • A record-line script truncated CYCLE.md (open(P,'w') ran before its read failed). It was recovered from git plus the other session's agent transcript, and left an empty commit 275f14fe. Compute every new content before opening anything for write.
  • update-consumers.sh doesn't know the Fable app; add it while c99's apps are live.

Handed on: sparkline data that looks synthetic (Mark: "leave for now"; options are Claude Code usage logs as a fixture shape, a fixed 30/90-day trend window, or a seed regeneration); /api/history (c92 day-series §B) for the projected installs trend; Govern / Secure split (Later); Cost Center's home (/cost, un-EXP); pushing the Fable branch and the design deploy (Mark's call).

Inherited from c92 (what this sheet builds on or reverses)

  • Nav: c92 team-nav (2026-09-26, team-nav-prompt.md). The sidebar today is c92's work: THE FLEET = Fleet Overview · Inventory · Team; the rule "Inventory = what's in the fleet (equipment), Team = who and from where"; /team/* routes with a redirect middleware; Fleet Overview on /fleet; DesignInProgress landings. Plan 01 restructures this tree. Renaming Team to Organization and folding the Fleet into Monitor revises that ruling, so the record line has to say what survives.
  • Trend: c92 day-series §B–§F, never executed (day-series-plan.md). The sheet's biggest theme ("trend on all of these", cost history per team) is what day-series planned: a served /api/history. That endpoint doesn't exist (:7882/api/history → 404, checked 2026-10-01), and the backend is read-only on the Fable branch. Plans 02 and 03 must separate trends that are honest today (client-side over served rows, or the fleet-wide /api/trends) from trends blocked on day-series §B, and follow its honest-sparkline idiom (em-dash below two known points, no projected series).
  • Vocabulary and removals in force: Harness → Harness install → Session (2026-09-24). Devices are gone and are not Endpoints (2026-09-25).

Visual record

33 captures — heroes, page slices, and full-page renders. Click to view.

Earlier captures

2026-10-01· 24 captureslatest
Teams (solo) — before todos-2026-09-30 plan 01
Teams (solo) — before todos-2026-09-30 plan 01 history · 14 moments →
Sessions listing — before todos-2026-09-30 plan 01
Sessions listing — before todos-2026-09-30 plan 01 history · 9 moments →
Teams (solo) — after todos-2026-09-30 plan 01
Teams (solo) — after todos-2026-09-30 plan 01 history · 14 moments →
Activity listing (was Sessions) — after todos-2026-09-30 plan 01
Activity listing (was Sessions) — after todos-2026-09-30 plan 01 history · 9 moments →
Activity listing (was Sessions) — after todos-2026-09-30 plan 02
Activity listing (was Sessions) — after todos-2026-09-30 plan 02 history · 9 moments →
Teams (solo) — after todos-2026-09-30 plan 03
Teams (solo) — after todos-2026-09-30 plan 03 history · 14 moments →
Users (solo) — before todos-2026-09-30 plan 04
Users (solo) — before todos-2026-09-30 plan 04 history · 7 moments →
Users (solo) — after todos-2026-09-30 plan 04
Users (solo) — after todos-2026-09-30 plan 04 history · 7 moments →
Activity listing (was Sessions) — c103 final (2026-10-01, after corrections + layer 0.9.24)
Activity listing (was Sessions) — c103 final (2026-10-01, after corrections + layer 0.9.24) history · 9 moments →
Teams (solo) — c103 final (2026-10-01, after corrections + layer 0.9.24)
Teams (solo) — c103 final (2026-10-01, after corrections + layer 0.9.24) history · 14 moments →
Users (solo) — c103 final (2026-10-01, after corrections + layer 0.9.24)
Users (solo) — c103 final (2026-10-01, after corrections + layer 0.9.24) history · 7 moments →
Nav — Monitor · Govern + Secure · Admin (c103 plan 01 + Mark's reorder)
Nav — Monitor · Govern + Secure · Admin (c103 plan 01 + Mark's reorder)
c103 full
history · 14 moments →
c103 full
history · 9 moments →
c103 full
history · 14 moments →
c103 full
history · 9 moments →
c103 full
history · 9 moments →
c103 full
history · 14 moments →
c103 full
history · 7 moments →
c103 full
history · 7 moments →
c103 full
history · 9 moments →
c103 full
history · 14 moments →
c103 full
history · 7 moments →
c103 full

Now + Trend — Variations (c103 plan 03 Part B)

2026-10-01· 9 captures
Seed snapshot (Meridian, captured 2026-10-01), 7d: §0 replicates Part A — Spend $331 rolled down to four teams, the per-day spark, the Spend trend column
Seed snapshot (Meridian, captured 2026-10-01), 7d: §0 replicates Part A — Spend $331 rolled down to four teams, the per-day spark, the Spend trend column open page →
§2 at 200 people, 30d, per-part ink: twelve teams roll down 6 + rest, seven ways (R1 today → R7 the rows' history stacked above them, Σ stack = Σ rows), each in its DataMachine seat, at 200 and at 160 × 14
§2 at 200 people, 30d, per-part ink: twelve teams roll down 6 + rest, seven ways (R1 today → R7 the rows' history stacked above them, Σ stack = Σ rows), each in its DataMachine seat, at 200 and at 160 × 14 open page →
§4 M4 Models, provider → model, 200 people, 30d: stacked area, 100 % weeks, small multiples, bump chart (claude-opus-5 overtakes), nested harness ink, breakdown + chart pair, Activity's history row
§4 M4 Models, provider → model, 200 people, 30d: stacked area, 100 % weeks, small multiples, bump chart (claude-opus-5 overtakes), nested harness ink, breakdown + chart pair, Activity's history row open page →
1,500 people, 40 % of days not collected, a spike day: every gap drawn as a gap or an em-dash, never a zero
1,500 people, 40 % of days not collected, a spike day: every gap drawn as a gap or an em-dash, never a zero open page →
§6 degradation lab at 200 people, 30d: every form × S2–S6, three auto-checks per cell (D direction · M magnitude · R driver)
§6 degradation lab at 200 people, 30d: every form × S2–S6, three auto-checks per cell (D direction · M magnitude · R driver) open page →
Seed at 24h: one served day — every trend is an em-dash and the tile drops its spark (the case for a fixed-90d trend window)
Seed at 24h: one served day — every trend is an em-dash and the tile drops its spark (the case for a fixed-90d trend window) open page →
c103 full
open page →
c103 full
open page →
c103 full
open page →

In the archive

Artifacts

Qpoint Brand Style Guide