Skip to content

feat(charts): session and project analytics charts with compaction tracking (schema v7) - #40

Merged
yorch merged 4 commits into
mainfrom
claude/session-project-metrics-charts-wmgnqd
Jul 23, 2026
Merged

yorch merged 4 commits into
mainfrom
claude/session-project-metrics-charts-wmgnqd

Conversation

@yorch

@yorch yorch commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

Session- and project-level charts across both frontends, with compaction detection end-to-end (parse → analyze → chart → index → rollup), plus a full multi-angle code-review/simplify pass applied.

feat: session charts + compaction capture

  • Compaction capture (events.ts, analyze.ts): system/compact_boundary events recorded into SessionAnalysis.compactions with trigger, preTokens, isSidechain, inherited; isCompactSummary-only files (older Claude Code) fall back to timestamp-only records. Boundary→summary pairs dedupe per chain kind, so a subagent streaming through a main-chain compaction can't double-count.
  • chart-series.ts (new, bun-free like stats-types.ts): context-window fill per main-chain call (the sawtooth), cumulative burn with sidechain split (timestamp-less calls keep their stored position), per-turn series — plus the canonical isOwnCompaction/summarizeCompactions split shared by every surface.
  • Web session Charts tab: context chart with dashed compaction markers (trigger/preTokens/offset tooltips), cumulative cost with teal subagent line, per-turn bars with metric toggle. TUI charts mode (c): braille context chart with ▼ marker row, cost-per-call and per-turn sparklines.
  • Machine-written compact summaries no longer open phantom turns (isRealPrompt excludes isCompactSummary, per community parser guidance); transcripts render them as labeled system items.

feat: project-scoped charts + distributions

  • spendByDay/modelMixByDay/sessionScatter/costDistribution take an optional projectId (via one shared projectScope/scopedAll helper); projectTrends() bundles the six chart series for /api/projects/:id/trends (per-project memo against the index fingerprint; unknown ids 404 so the cache keyspace stays bounded).
  • New per-project toolUsage()/turnDepthStats() built on the same row-fold helpers analyticsRollup uses, so portfolio and project surfaces can't drift.
  • Web project page: burn panel (metric × granularity), session-cost histogram (median/p90/top-decile), turn-depth buckets, tool mix with error rates, model mix, cost×duration scatter — via chart components extracted into shared trend-charts.tsx (one SVG geometry kit for the whole SPA; Trends page refactored onto it).
  • TUI project preview: weekly burn sparkline + cost/depth distribution ramps, live per-highlight queries.

feat: compactions in the index (schema v7)

  • Boundaries preceding any API call are flagged inherited — continuation files copy the parent's final boundary, so counting them would double-count one compaction across two rows.
  • Schema v7: compactions INT (own main-chain only) + compactions_json detail. v6 indexes drop and rebuild on open (standard migration path — first cc-analyzer index after this re-parses everything).
  • compactionUsage() → /api/analytics: auto/manual/unknown/subagent/inherited split (single-sourced from the JSON via the shared predicate) + per-project pressure table in the web Tools view; project rows and the TUI preview carry the summed count.

fix: multi-angle review pass (8 finder angles + verification, all confirmed findings applied)

  • Burn-series ordering bug (epoch-ms vs array-index sort keys) — fixed.
  • Compaction double-count under sidechain interleaving — fixed.
  • Inherited boundaries miscounted as the session's own in both frontends, with a false "without timestamps" caption — fixed via the shared predicate.
  • Trigger-less legacy compactions inflating the "auto" bucket — now reported as "unknown".
  • Tools-view empty state hiding inherited-only portfolios; TUI preview hiding distributions for single-week projects — fixed.
  • Unbounded per-project memo keyspace — unknown ids 404 first.
  • Duplication removed: bucketSeries/metricValue/weeklySeries hoisted to bun-free core; one SVG geometry kit; shared tool/depth folds; markerRow simplified; derivable index fields dropped; BurnPanel/ScatterPanel re-memoized; marker placement is one cursor pass.
  • Deliberately not taken (documented trade-offs): folding projectTrends into a single scan and compactionUsage into the rollup scan (endpoints are fingerprint-memoized; scans are indexed/filtered), merging /api/trends?project= (distribution/depth/tools are project-only extras), moving TUI preview queries out of render (sub-ms indexed lookups, memoized per highlight).

Format assumptions validated against real-world sources

  • compact_boundary shape (compactMetadata: { trigger, preTokens }): fsck.com deep dive, dev.to /compact write-up, vibe-replay.
  • isCompactSummary synthetic prompt immediately follows the boundary; parsers should skip it as machine-generated (fsck.com).
  • Context = input + cache_creation + cache_read — same formula as Claude Code's statusline used_percentage, ccusage, ccstatusline (docs, ccusage#480).
  • Subagents run separate context windows and compact independently (claude-code#16944).
  • Continuation files duplicate the parent's final boundary (fsck.com) — the inherited flag.

Tests & gates

279 tests across 33 files (compaction capture incl. interleaving/inherited/aggregate mode, chart-series ordering + marker placement + canonical split, project-scoped rollups, v7 indexer roundtrip, API routes incl. the 404 guard, TUI/web render smoke tests, transcript labeling). bun test, bun run lint, both typechecks, and bun run build:web all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq

claude added 3 commits July 22, 2026 21:10
…, per-turn

Capture context compactions in the analyzer (system/compact_boundary events
with trigger + preTokens, falling back to isCompactSummary prompts for older
Claude Code files, deduping the boundary→summary pair) and expose them as
SessionAnalysis.compactions.

Add src/core/chart-series.ts, a bun-free module of session chart series
builders shared by both frontends: context-window tokens per main-chain API
call (the compaction sawtooth), cumulative cost with a sidechain split, and
per-turn cost/tokens/calls.

Web: new per-session Charts tab (context chart with dashed compaction
markers, cumulative cost, per-turn bars with a metric toggle) plus a
compactions row in the summary; Seg extracted for reuse. TUI: new charts
mode in the session detail screen (braille context chart with a ▼ marker
row, cost-per-call and per-turn sparklines) and a compactions summary line.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq
…ext markers

Research against real-world session parsers confirmed compact_boundary events
also occur inside subagent transcripts (isSidechain: true) — a subagent's
compaction compacts its own context window, not the main chain's. Capture the
flag on Compaction records, pair boundary→summary per chain kind, skip
sidechain compactions when placing context-chart markers, and label them
separately in the web and TUI summaries.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq
…hema v7)

Slice 2 — project charts: spendByDay/modelMixByDay/sessionScatter/
costDistribution take an optional projectId; projectTrends() bundles them
with the new per-project toolUsage() and turnDepthStats() for
/api/projects/:id/trends (memoized per project against the index
fingerprint). The web project page gains burn (metric + granularity
toggles), model mix, cost/duration scatter via chart components extracted
from Trends into trend-charts.tsx; the TUI project preview gains a weekly
burn sparkline computed live from the index.

Slice 3 — distributions: per-project session-cost histogram, turn-depth
buckets, and tool mix on the web project page; compact distribution ramps
in the TUI project preview.

Slice 4 — compactions in the index (schema v7): the analyzer flags
boundaries that precede any API call as inherited (continuation files copy
the parent's final boundary); the index stores compactions_json plus a
compactions INT column counting only own main-chain compactions, so one
compaction never counts in two session rows. compactionUsage() rolls up
portfolio pressure (auto/manual/subagent/inherited split + per-project
share) for /api/analytics, rendered in the web Tools view; project rows
and the TUI preview carry the summed count. Existing v6 indexes are
dropped and rebuilt on open, as with every schema bump.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq
@yorch yorch changed the title Session charts: context-window fill with compaction markers, cumulative burn, per-turn bars Session + project charts: context/compaction, burn, distributions, compaction indexing (schema v7) Jul 22, 2026
…and chart series

Correctness:
- buildBurnSeries no longer mixes epoch-ms with array indices when sorting;
  a timestamp-less call inherits its predecessor's timestamp and keeps its
  stored position instead of jumping to the front of the burn series.
- The boundary→summary compaction pair now only closes on an assistant line
  of the same chain kind, so a subagent streaming while the main chain
  compacts no longer records the compaction twice.
- One canonical own-compaction rule (isOwnCompaction/summarizeCompactions in
  chart-series.ts) is now used by the indexer, the rollup, the markers, and
  both frontends' labels — inherited continuation-file boundaries no longer
  count as the session's own, and the false '(some without timestamps)'
  caption is gone; inherited/subagent records are labeled explicitly.
- compactionUsage reports trigger-less legacy compactions as 'unknown'
  instead of inflating 'auto', and is single-sourced from compactions_json.
- The web Tools compaction empty-state accounts for inherited-only
  portfolios; the TUI project preview no longer hides distribution ramps
  for single-week projects.
- /api/projects/:id/trends 404s unknown ids before touching the memo cache,
  keeping its keyspace bounded by real projects.
- Machine-written compact summaries no longer open phantom turns
  (isRealPrompt excludes isCompactSummary); transcripts render them as
  labeled system items.

Reuse/simplification:
- bucketSeries/metricValue/weeklySeries hoisted into bun-free
  stats-types.ts and shared by the TUI and the SPA.
- One SVG geometry kit (xScale/linePath/areaPath, shared constants) in
  trend-charts.tsx consumed by LineChart, SessionCharts, and SkillSpark.
- toolUsage/turnDepthStats and analyticsRollup now share row-fold helpers.
- markerRow arithmetic simplified; derivable index fields dropped from
  chart-series points; BurnPanel/ScatterPanel memoized; context markers
  placed by one cursor pass instead of repeated findIndex.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq
@yorch yorch changed the title Session + project charts: context/compaction, burn, distributions, compaction indexing (schema v7) feat(charts): session and project analytics charts with compaction tracking (schema v7) Jul 23, 2026
@yorch
yorch marked this pull request as ready for review July 23, 2026 13:43
@yorch
yorch merged commit ca6522e into main Jul 23, 2026
3 checks passed
@yorch
yorch deleted the claude/session-project-metrics-charts-wmgnqd branch July 23, 2026 13:47
yorch added a commit that referenced this pull request Jul 23, 2026
Release-prep for **v0.6.0** (minor), covering everything merged since
v0.5.0:

- **feat(charts):** session and project analytics charts with compaction
tracking; index schema bumped to v7 (#40)

Bumps `package.json` 0.5.0 → 0.6.0 so the binary's reported version
matches the tag. After merge, tagging `v0.6.0` on `main` triggers the
release workflow (5 platform binaries + `SHA256SUMS`).

Note: the schema v7 change is a rebuild of the disposable local index
cache — no user action needed beyond the automatic reindex.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
yorch added a commit that referenced this pull request Jul 23, 2026
…compaction uuid dedupe (schema v8) (#42)

Follow-ups to #40, covering the five items left on the charts roadmap,
plus a full 8-angle code-review/simplify pass applied on top.

## 1 — Context-window limit line

`ModelPricing.maxInputTokens` (LiteLLM `max_input_tokens`; also added to
the bundled snapshot — a 23-line diff, original number notation
preserved) flows through `resolveModel` → `ModelUsage.contextLimit` →
`ContextSeries.contextLimit`. **Both** frontends now scale the context
chart to the window so headroom is the visible signal: the web draws a
dashed limit line with "% of window" in header + tooltips, and the TUI
braille chart takes the limit as its ceiling with the same percentage in
its header — one shared `pctOfLimit()` in bun-free core for all label
math. Safeguards from review: the limit is suppressed when the peak
meaningfully exceeds it (a 1M-window variant priced by the 200k
family-heuristic entry must not render "375% of window"), and the
pricing cache is now **format-versioned** so a pre-upgrade cache (which
lacks `maxInputTokens`) refreshes instead of silently hiding the feature
for up to 7 days.

## 2 — Compacted-session ground truth (fixture + E2E)

New `test/fixtures/compacted-session.jsonl` with the research-documented
field shapes (`compact_boundary` + `compactMetadata`, adjacent
`isCompactSummary` prompt, sidechain burst, real uuids/timestamps) and
an end-to-end test across **parse → analyze → chart series → index →
portfolio rollup**, including indexing a copied session file to prove
the uuid dedupe. The final eyeball against a real `~/.claude` stays a
local step.

## 3 — Deferred perf/structure items

- `projectTrends()` folds its three JSON-blob series in **one pass**
over the project's rows (model mix via a new shared fold); SQL
aggregates stay in SQLite. The now-orphaned `toolUsage()` was deleted.
- The TUI project preview is db-free: `projectPreviewStats()` is
computed at the screen boundary (memoized on the stable `projectId`) and
passed as plain props.
- **Considered and not taken**: merging `/api/projects/:id/trends` into
`/api/trends?project=` — the shapes genuinely differ and both derive
from shared `stats-types` rows.

## 4 — Test hygiene

`test/tui/{trends,insights,tools}.test.tsx` build rows through the
shared `insertSession` helper. The review pass also extracted
**`tempClaudeDir()`** (`test/helpers/claude-dir.ts`) and migrated all
five copies of the temp-dir/env boilerplate onto it (`mkdtempSync`,
leak-proof env restore).

## 5 — Cross-file compaction dedupe (schema v8)

`Compaction` records carry the boundary event's `uuid`;
`compactionUsage()` filters **every category** (own, subagent, inherited
— a copied file duplicates them all) through a shared
`dedupeCompactions()` before the canonical `summarizeCompactions()`
split, with `ORDER BY path` for deterministic attribution. **Schema
bumps to v8**: uuid is load-bearing in `compactions_json`, and without
the forced rebuild the incremental indexer would leave v7 rows uuid-less
and the dedupe would silently no-op on existing installs.
`byProject.compactions` stays the raw SUM-able per-row column and is
documented as such.

## Review pass (8 finder angles + verification, all confirmed findings
applied)

Fixed: missing schema bump (v8); un-versioned pricing cache; partial
dedupe (own-only → all categories) + second trigger-classification site
+ nondeterministic scan order; TUI/web headroom-scaling divergence
(`brailleChart` ceiling); ">100% of window" from heuristic limits; three
duplicated percent formulas → `pctOfLimit`; duplicated per-point limit
representation dropped; `bundled-pricing.json` notation churn reverted
to a 23-line diff; orphaned `toolUsage()` deleted (CLAUDE.md updated);
preview memo keyed on `projectId` string; uuid conditional-spread
ceremony removed; fifth copy of the claude-dir test boilerplate
extracted into a helper and all five migrated.

## Gates

293 tests across 34 files, `bun run lint`, both typechecks, and `bun run
build:web` all green.

**Note for reviewers**: schema v8 means the first `cc-analyzer index`
after this lands rebuilds the index (established migration path;
required for the uuid dedupe to work on existing installs).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012FVf8TYr2EAWW3NjN3Pdbq

---------

Co-authored-by: Claude <[email protected]>
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.

2 participants