feat(insights): /usage-style profile + cost attribution; scanner perf overhaul#2
Merged
Merged
Conversation
… overhaul
Insights tab — new all-time Profile section
- 2×4 stat grid: sessions / messages / total tokens / active days,
current & longest streak, peak hour, favorite model.
- 13-week GitHub-style contribution calendar (blue palette).
- Fun-fact callout ("~N× Harry Potter") tied to lifetime tokens.
- Replaces the older weekday × hour heatmap on the Insights tab.
Cost tab — new attribution sections
- By context size: ≤50k / 50–150k / >150k bands. Claude-only (Codex
records are delta-of-cumulative, not absolute prompt size).
- Usage attribution tables: Skills / Subagents / Plugins / MCP servers,
read straight from Claude's `attribution*` fields on each assistant
line — so percentages are an exact group-and-sum.
- Both follow the right-pill range (today / 7d / 30d) and the
cost/tokens metric, like the existing model/project breakdowns.
Scanner perf — boot peak RSS ~1.3 GB → ~75 MB on this 711 MB corpus
- Per-line autoreleasepool in both scanners so JSONSerialization's
NSDictionary/NSString tree drains immediately instead of piling up
across files (Aggregator.run runs in a detached Task with no
natural pool drain).
- Shared Scanner across Claude + Codex so the on-disk cache is
decoded once per Aggregator.run() instead of twice.
- Cache format JSON → binary property list. PropertyListDecoder reads
directly into the Swift struct without an intermediate NSDictionary
tree, and the on-disk file shrinks ~62% (18 MB → 6.9 MB here).
cacheVersion 4 → 5 (forces one full rescan on next launch).
Privacy boundary unchanged: every new data point is derived locally
from already-scanned records. No new I/O sources, no new network paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Borrows two ideas from Claude Code's
/usageand Claude Desktop's Overview, while keeping CodingBar's "100% local" promise — every new data point is derived from already-scannedRawRecords, no new I/O sources, no new network paths.Ships in three layered slices:
Insights tab — all-time Profile
Cost tab — usage attribution
Two new sections, both Claude-only (Codex carries no attribution tags and its tokens are deltas of a running total, not absolute prompt size), both range-aware and metric-aware so they follow the existing right-pill + cost/tokens toggle.
≤50k / 50–150k / >150kbands using the absolute prompt size from each request'susageblock. Mirrors/usage's "what's contributing to your usage" lens; on this machine the >150k band came in at 53% of 30-day Claude spend.attribution*fields on each assistant line withusage, so percentages are an exact group-and-sum — not a heuristic. Honest "% of usage" denominator = Claude total in range, so shares are independent and don't sum to 100 (most turns carry no attribution at all).Scanner perf — boot peak RSS ~1.3 GB → ~75 MB
Before this branch a fresh launch on a 711 MB / 1623-file / 51k-record corpus would spike to ~1.3 GB resident before settling back to ~190 MB. After:
.app).app)Three orthogonal fixes:
autoreleasepoolin both scanners.JSONSerializationreturns autoreleasedNSDictionary/NSStringtrees; in a tight scan loop running inside a detachedTaskthere's no natural pool drain, and intermediates pile up across files. Wrapping each line drains immediately.Scanneracross Claude + Codex. Previously each provider instantiated its ownScannerand re-decoded the same cache file twice perAggregator.run(). Now oneScanneris created in the aggregator and passed to both.PropertyListDecoderreads directly into the Swift struct without the giant intermediateNSDictionarytree thatJSONDecoderbuilds viaJSONSerialization. Cache file also shrinks ~62%.cacheVersionbumped 4 → 5 (forces one full rescan on next launch; pre-release so no user impact).Privacy boundary
Unchanged. Two network paths only, both pre-existing: Claude/Codex quota GETs (TTL-cached, the user's own OAuth token) and a user-initiated GitHub releases GET. No telemetry, no log uploads.
Test plan
swift buildclean (debug + release)make test(--self-test) — ALL PASS--dump-jsonon real local corpus — profile (sessions=1398, activeDays=104, streak=32, peakHour=16, favorite=Opus 4.8), context attribution (>150k = 52/55/53% across today/7d/30d), usage attribution (workflow-subagent 14.2%, playwright MCP 10.7%, orchestration skill 4.8% on 30d) all match/usage-style numbers--render-panel— 2×4 grid + calendar + fun fact render correctly.appboot peak measured at 49–75 MB resident (down from ~1.3 GB)bplist00magic), size 6.86 MBProfileBuildertests + new attribution-parsing test added toSmokeTests.swiftdocs/assets/panel-insights.png/panel-overview.pngreference shots (left to a follow-up sincedocs/assets/*.pngalready had pre-existing uncommitted changes on this working tree)🤖 Generated with Claude Code