feat: About and System dashboard areas, plus history-window and usage fixes - #123
Open
pacphi wants to merge 13 commits into
Open
feat: About and System dashboard areas, plus history-window and usage fixes#123pacphi wants to merge 13 commits into
pacphi wants to merge 13 commits into
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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>", zerousage) 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 literalmarker too, with
SCHEMA_VERSIONbumped so cached sessions re-derive.feat(dashboard)history window — Observability's History scope was capped by the livetailer'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 isnever rendered as zero: figures carry measured/partial/unknown provenance and print
>= Nwhenbounded. Ships
GET /api/systemandak 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, sincethat's where a pasted prompt or token would live), plus a best-effort inline
Add-TypeP/Invokewalking
NtQueryInformationProcess→ PEB →RTL_USER_PROCESS_PARAMETERSfor true per-processcwd. 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.jsonstill declares none.Two Windows-specific things worth reviewer attention:
.ps1lives undersrc/lib/live/, beside its consumer, not underscripts/.filesshipssrc/wholesale but neverscripts/, so the original placement meantnpm packcarried no.ps1at all and every npm-installed Windows user would have lost theentire census. A guard test asserts the placement on every platform.
tests/kit/win-process-survey-live.test.mjsis the only test that executes the survey. Theother 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 testexit 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 systemvisibly renders honest degradation.The Windows census is unit-tested against fixtures locally; this PR's
windows-latestlegs areits first real execution.
Notes for review
new contract; both ADRs record it as a deliberate amendment.
dashboard.test.cjs's self-contained assertion was replaced with a sharedassertSelfContained()helper: the old regex conflated "no external fetch" with "no httpsstring" 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.mjsdeclares — and stillbans external script/stylesheet/img. Strictly stronger, not weaker.
.git/worktrees/<name>/gitdirto identify orphaned worktrees.ADR-0025 §7 sanctioned
.git/config's remote URL but not this; it's documented as the sameclass of pointer metadata, and there's a
detectWorktreesflag if you'd rather read theinvariant literally.
docs/MANAGED-TOOLS.mdhas no aidefence row and noruvector entry. The parity gate passes today via its own mapping.
🤖 Generated with Claude Code