Skip to content

feat: About and System dashboard areas, plus history-window and usage fixes - #123

Open
pacphi wants to merge 13 commits into
mainfrom
feat/history-window-and-system-tab-design
Open

feat: About and System dashboard areas, plus history-window and usage fixes#123
pacphi wants to merge 13 commits into
mainfrom
feat/history-window-and-system-tab-design

Conversation

@pacphi

@pacphi pacphi commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Nine commits: two fixes, one feature from earlier in the session, then the About and System
areas implemented against their (now accepted) ADRs.

What's here

fix(usage) — Claude Code's dropped-connection placeholder (model: "<synthetic>", zero
usage) was slipping past the exception filter on builds that emit it without
isApiErrorMessage, so it surfaced as a real $0 "model in play". Now matched on the literal
marker too, with SCHEMA_VERSION bumped so cached sessions re-derive.

feat(dashboard) history window — Observability's History scope was capped by the live
tailer's moving window (256 newest files / 100-session projection). It now browses by calendar
window (1d…1y, all) via a one-shot historySnapshot() that never touches live tailer state.

About (ADR-0026) — a leftmost area introducing every component ak installs or configures.
Editorial content is curated and versioned; state chips join client-side against the status
payload the dashboard already polls, so there's no new endpoint and no probing, prose never
claims runtime state, and a failed join degrades chips to unknown while content still renders.
A parity test asserts registry↔directory completeness in both directions, so a future managed
tool can't ship without its card. Ships ak about.

System (ADR-0025) — machine footprint: install, storage, runtime, catalog, projects. Two
tiers — a cheap TTL-cached read and an explicit single-flight deep scan persisted with an
asOf. Nothing scans on dashboard open; rescan is manual with a staleness nudge. Unknown is
never rendered as zero: figures carry measured/partial/unknown provenance and print >= N when
bounded. Ships GET /api/system and ak system.

Windows is real, not "unsupported"

The runtime census previously threw on win32. It now ships a guaranteed floor via
Get-CimInstance Win32_Process (pid/ppid/CPU/RSS/uptime — argv deliberately excluded, since
that's where a pasted prompt or token would live), plus a best-effort inline Add-Type P/Invoke
walking NtQueryInformationProcess → PEB → RTL_USER_PROCESS_PARAMETERS for true per-process
cwd. A bitness mismatch is detected rather than read through with wrong offsets, and any
probe failure degrades to an honest "not attributable" — never a fabricated path, and never
taking the census down with it. No dependency added; package.json still declares none.

Two Windows-specific things worth reviewer attention:

  • The .ps1 lives under src/lib/live/, beside its consumer, not under scripts/.
    files ships src/ wholesale but never scripts/, so the original placement meant
    npm pack carried no .ps1 at all and every npm-installed Windows user would have lost the
    entire census. A guard test asserts the placement on every platform.
  • tests/kit/win-process-survey-live.test.mjs is the only test that executes the survey. The
    other Windows tests inject a fake runner and pass identically on macOS, so they prove the
    parsers and nothing else. This one spawns real PowerShell on win32 and checks the census and
    the PEB walk against ground truth we already hold — the test process's own pid and cwd — so a
    wrong PEB read can't hide behind a plausible-looking path.

Verification (run locally on macOS)

pnpm test exit 0 · dashboard suite 72/72 · browser suite 241/241 · eslint clean ·
markdownlint 0 issues · internal links 0 errors · doc-citations 2/2 · runtime dependencies
still absent
. Both CLIs smoke-tested; ak system visibly renders honest degradation.

The Windows census is unit-tested against fixtures locally; this PR's windows-latest legs are
its first real execution.

Notes for review

  • This makes five primary areas. ADR-0005's "exactly three" assertion is updated to state the
    new contract; both ADRs record it as a deliberate amendment.
  • dashboard.test.cjs's self-contained assertion was replaced with a shared
    assertSelfContained() helper: the old regex conflated "no external fetch" with "no https
    string" and failed on About's curated link pills. The replacement pins the invariant to the
    directory itself — every external URL must be one about-directory.mjs declares — and still
    bans external script/stylesheet/img. Strictly stronger, not weaker.
  • The storage collector reads .git/worktrees/<name>/gitdir to identify orphaned worktrees.
    ADR-0025 §7 sanctioned .git/config's remote URL but not this; it's documented as the same
    class of pointer metadata, and there's a detectWorktrees flag if you'd rather read the
    invariant literally.
  • Pre-existing gap, not introduced here: docs/MANAGED-TOOLS.md has no aidefence row and no
    ruvector entry. The parity gate passes today via its own mapping.

🤖 Generated with Claude Code

pacphi added 13 commits August 6, 2026 13:13
…orMessage

Some builds emit Claude Code's dropped-connection placeholder turn
(model: "<synthetic>", zero usage) without setting isApiErrorMessage,
so it slipped past the exception filter and surfaced as a real \$0
"model in play" on the scorecard. The literal model marker is now
checked alongside the flag, SCHEMA_VERSION bumps to 9 so cached
sessions re-derive, and a regression test covers the flagless shape.

Also re-anchors the usage-doc file:line citations shifted by this
file's line movement (doc-citations gate).
Observability's History scope was limited to the live tailer's moving
window (256 newest transcript files, 100-session projection). It now
browses retained sessions over an explicit calendar window — 1d, 7d,
14d (default), 1mo, 3mo, 6mo, 1y, all — like Usage's day chips.

- discoverJsonl() gains an optional sinceMs mtime cutoff
- LiveSessionsService.historySnapshot({sinceMs}): a one-shot scan with
  its own projection (never touches live tailer state), swept with
  all-zero windows so unterminated sessions read as stale, not live
- GET /api/live/history?window=<token>, same publicLivePayload
  scrubbing as /api/live; 501 when the service lacks historySnapshot
- window chips in the History sub-nav; History renders from a separate
  state.historySnapshot bucket so live SSE deltas can never clobber it

The UI harness also allowlists /api/live/intelligence EventSource
teardown aborts (pre-existing flake — Chromium reports deliberate
stream closes as ERR_ABORTED, same as the existing events/transcripts
entries) and gives LIVE_STUB a historySnapshot.
…esign mock

Drafts for review; nothing implemented yet.

- ADR-0025 (Proposed): a Machine footprint bounded context and a fourth
  System primary area (Summary / Storage / Runtime / Catalog /
  Projects), tiered honest collection with a persisted asOf snapshot,
  the initial metric taxonomy (install, runtime, storage, catalog,
  per-project LOC/disk, git-remote links), GET /api/system + ak
  footprint delivery, advisory-only reclaimables, and a documented
  absolute-path exception
- docs/ddd/machine-footprint.md: purpose, boundaries against Usage /
  Observability / Project intelligence / Integration management, the
  FootprintSnapshot model, measurement semantics, 12 invariants, and
  proposed ubiquitous-language terms
- docs/assets/system-tab-mock.html: self-contained both-theme mock of
  the System area on the dashboard's own tokens, every card annotated
  with its chart-form rationale; illustrative data only
…design mock

Drafts for review; nothing implemented yet.

- ADR-0026 (Proposed): a leftmost About primary area introducing every
  component ak installs or configures — curated editorial copy joined
  with existing detection facts (no new endpoint, no probing), a
  registry↔directory parity gate so a managed tool cannot ship without
  its About card, official host marks + honest monogram tiles, and
  outbound user-initiated links inside the zero-egress contract
- docs/ddd/component-directory.md: the editorial/detection split as
  the load-bearing boundary, the new-user register contract (~50-word
  plain-language paragraphs, no runtime claims in prose), 10
  invariants, and proposed ubiquitous-language terms
- docs/assets/about-tab-mock.html: self-contained both-theme mock on
  the dashboard's own tokens — hero orientation strip with a
  how-it-fits map, category card grid (hosts first, honest
  not-installed state shown, configured surfaces with manage:
  commands), per-section design notes, and an annotated card anatomy
Implements ADR-0026. A curated directory module carries each component's
editorial identity — tagline, one plain-language paragraph, source/npm/docs
links, icon, category — rewritten from verified upstream sources rather than
assumption. It collects nothing: state chips join client-side against the
status payload the dashboard already polls, so prose never claims runtime
state and a failed join degrades chips to unknown while content still renders.

The parity test is the point: every managed tool must have exactly one entry
and no entry may exist for something ak neither installs nor configures, so a
future tool cannot ship without its card.
…census

Implements ADR-0025's collectors: a bounded walker (never follows symlinks,
one bad subtree degrades to unknown), install/storage/catalog/projects, and
an ephemeral runtime census. Unknown is never rendered as zero — every figure
carries measured/partial/unknown provenance, and lower bounds print as ">= N".

Windows is real rather than unsupported. A shipped PowerShell script gives the
guaranteed floor via Get-CimInstance (pid/ppid/CPU/RSS/uptime, argv
deliberately excluded), and a best-effort inline P/Invoke walks
NtQueryInformationProcess -> PEB -> RTL_USER_PROCESS_PARAMETERS for true cwd.
A bitness mismatch is detected rather than read through with wrong offsets,
and any probe failure degrades to an honest "not attributable" — never a
fabricated path, and never taking the census down with it. No dependency
added; package.json still declares none.

The script lives beside its consumer under src/ because `files` ships src/
wholesale — under scripts/ it would never have reached an npm-installed
Windows user at all. A guard test asserts that placement on every platform,
and three live tests execute the real PowerShell on windows-latest, checking
the census and the PEB walk against this process's own pid and cwd.
…m CLI

Cheap tier (census + known-file stats + snapshot carry-forward, TTL-cached)
on every read; deep tier explicit and single-flight so concurrent refreshes
attach to the in-flight scan. The deep result persists with an asOf; a missing
or corrupt snapshot reads as "never measured", never as zeros. Rescan is
manual only — nothing scans on dashboard open.

The payload deliberately carries absolute paths, unlike /api/live's leaf-only
reduction, because in this domain the path is the answer; file contents are
never read, so nothing sensitive can travel with them.

dashboard.test.cjs's self-contained assertion is replaced with a shared
assertSelfContained() helper. The old regex conflated "no external fetch" with
"no https string" and so failed on About's curated link pills; the replacement
pins the invariant to the directory itself — every external URL must be one
about-directory.mjs declares — and still bans external script/stylesheet/img.
That is strictly stronger, and the browser suite independently asserts the run
requests nothing off the loopback origin.
About lands leftmost as the reading-order entry point; Overview remains the
default landing view, with a dismissible first-run nudge rather than a
hijacked view. System adds Summary/Storage/Runtime/Catalog/Projects with the
charted treatments from the design mock, a freshness label that nudges once a
snapshot goes stale, and honest empty states — "not measured yet" is never
rendered as a zero.

This makes five primary areas. ADR-0005's "exactly three stable primary areas"
assertion is updated to state the new contract, which both ADR-0025 and
ADR-0026 record as a deliberate amendment.
Both ADRs flip Proposed -> Implemented, and their "open points for review"
sections are rewritten as resolved decisions with rationale: tab and CLI both
"System" (ak system), Projects stays its own sub-view, manual-only rescan with
a staleness nudge, Windows gets a guaranteed census plus best-effort P/Invoke
cwd, About gets a dismissible nudge, ak about ships, six configured cards.

Both bounded contexts join the context map and ubiquitous language, the ADR
index gains rows and narrative, and DASHBOARD.md documents the two new areas
for users — including how to read ">= N" lower bounds, "not measured yet",
approximate LOC, and the platform differences. README's command list gains
ak about and ak system.

The DDD docs lose their draft banners and record what shipped rather than what
was planned, including limits stated honestly: daemon budget state, the
statusline two-row join, and unattributable Codex bytes.
… Windows CI

Three fixes from reviewing the running dashboard.

Mock leakage. The design mocks were written as reviewer-facing artifacts with
their rationale inline, then handed to the UI agent as the visual spec, so it
faithfully reproduced the annotations along with the design. Removed 4 About
"Design note" blocks, 11 System "Why …" blocks, the "Two bars, two colour jobs"
note, all 13 chart-type pills ("radial gauge", "ranked bars", …), the About
footer, and the page-wide read-only footer, plus the CSS those left orphaned.
Kept every genuine data caveat — "line counts are approximate", "durations are
session span", "secrets masked server-side" — because those qualify the numbers
rather than explaining the design. Also dropped the hero's installed tally,
which restated each card's own chip less precisely.

Version chips: 6 of 9 components reported a version, for three different
reasons. driftReport() walks npm globals only, so a host installed by
mise/brew/native (Claude Code) was invisible to it; agentdb is a real global
but pinned to ruflo's bundled version, so it is deliberately excluded from the
update banner and fell out of the array with it; aidefence ships inside ruflo
and has no global install at all. foldKnownVersions() adds all three from
structured probes — never scraped from a status row's prose — as outdated:false
so noticeHtml (which filters on outdated) still shows an empty banner. The host
probe costs ~300ms, so it sits behind a 5-minute in-process TTL rather than
riding every 30s poll. Now 9 of 9.

Windows CI: the new absolute-paths assertion compared a fixture path against
the raw JSON body. A Windows path carries backslashes, which JSON escapes on
the wire, so the check passed on POSIX and failed on all three windows-latest
legs for a payload that was correct — the sibling assertions that compare
parsed values were green throughout. Compares the encoded form now.
The CI quality gate went red on the previous commit: the param was annotated
Array<{pkg:string}> while the function pushes {pkg, installed, latest,
outdated}, so tsc rejected the object literal. The annotation was simply
narrower than the array driftReport() and the selfDrift/brain/ruvector folds
have always produced.

The return type keeps those fields OPTIONAL rather than required, because
incoming entries are passed through untouched — promising them as present
would be a second wrong annotation in the other direction.
…ontract

The UI suite went from 241/0 to 183/25 with no code change between the runs.
Cause: the fixture corpus is pinned to 2026-07-24 and the panel requests a
14-day window, so at 00:00 on 2026-08-07 the whole corpus aged out of its own
window. Proof, straight from the index: days=14 -> 0 sessions, days=30 -> 3.
One data-fixture check failed and 23 session-view assertions cascaded off it.

The kit suites avoid this by pinning `now` (usage-index.test.mjs says so in its
header), but this harness drives a REAL server against the real clock, so it
cannot. extendedCorpus() already copies the fixtures into a temp dir, so the
copy is shifted forward instead — by a WHOLE number of days, which preserves
every relative fact the assertions rest on: the 85-minute idle gap separating
the three time tiers, the worktree session nested inside another's span, and
each turn's local time-of-day for the punchcard's hour buckets. The checked-in
fixtures keep their literal dates, because the kit suites pin `now` against
exactly those.

The remaining failure was a real contract change, not a bomb: the hero no
longer counts detections, so asserting it says "unknown" tested behaviour that
was deliberately removed. It now asserts the actual contract — the hero states
only what ak MANAGES, making no detection claim in either direction, because
each card's own chip already carries per-component state and an aggregate could
only restate it less precisely.
…reclaimables

The collectors from the previous run were built and tested but never called —
index.mjs had no owner, so nothing composed them into the payload. Wiring them
up is most of this change; the rest is what wiring them exposed.

Now live (verified against GET /api/system, not asserted):
  - projects: 50 ever seen / 25 on disk / 21 git repos, de-duped across hosts by
    resolved real path. Was 4, because discovery reused discoverRuvfloProjects()
    — which requires .claude-flow/neural/ state and answers a different question
    that Intelligence still depends on, so it is left alone.
  - consumers: 80 roots, top 20 ranked, grouped by ecosystem. The panel called
    npx cache the #1 consumer at 6 GB; it is #12. The real leaders were entirely
    unscanned — Ollama 141 GB, LM Studio 49 GB, Hugging Face 36 GB, npm's
    _cacache 22 GB. Overlapping roots collapse by path so the list cannot go
    self-similar (~/.npm never appears beside its own _cacache).
  - snapshot persists consumers; runtime is still absent, and that allow-list is
    what structurally enforces the ephemeral-census invariant.
  - refreshDeep finally takes includeProjectTrees, which the caller was already
    passing into a zero-arity function. Sticky across rescans; default off,
    because one repository here is 175 GB and flattens every other row.

A HARD HANG, found only because the wider discovery reached it: the deep scan
parked forever at 0% CPU inside a Dropbox tree. Cloud providers leave evicted
placeholders — stat returns instantly, read blocks in the kernel until the
provider materializes the bytes, which never happens while it is signed out.
There is no timeout, so the scan never completed and the dashboard served a
17-hour-old snapshot. Worse, reading them silently pulls the file down. Guarded
by the allocated-blocks basis the DDD already names (blocks === 0 && size > 0):
placeholders are stat-ed, never opened. Zero false positives across 3,667 files
here, including sub-2KB files that APFS stores inline. The tree that hung
forever now returns in 50ms; a full scan takes 204s.

Also: Playwright was invisible on macOS (the scan looked only at the XDG and
Windows paths, so 1.86 GB read as a measured zero), and the RuvNet Brain was
under-reported by 85% because only kb/ was measured — the other 11 GB is five
dated kb.bak snapshots, now broken out rather than silently folded in.

Reclaimables grew from two detectors to cover those backups, npm's regenerable
cache, orphaned transcripts, and browser downloads — split into two safety
tiers that never sum: 'regenerable' for caches a tool refetches on demand, and
'review' for things like mise's 8 node versions, where recommending deletion of
a live runtime would be worse than saying nothing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant