Commit graph

1199 commits

Author SHA1 Message Date
Resham Joshi
fd4ede2bb3
Merge pull request #880 from ozymandiashh/fix/876-menubar-download-retry
Some checks are pending
CI / semgrep (push) Waiting to run
fix(menubar): retry transient release-asset download failures
2026-08-03 14:26:03 -07:00
Resham Joshi
18e5011fc3
Merge pull request #860 from avs-io/fix/post-0918-polish
post-0.9.18 polish: compare flags in the TUI, context id prefix, active-days label
2026-08-03 14:11:05 -07:00
AgentSeal
3212b865f7 Merge branch 'main' into fix/post-0918-polish
Resolves tests/dashboard.test.ts (union of both new describe blocks and imports).
2026-08-03 23:08:13 +02:00
Resham Joshi
8dd5a62801
Merge pull request #801 from EuanTop/fix/opencode-session-model
fix(opencode): read session fallback model from the real schema
2026-08-03 13:57:16 -07:00
AgentSeal
80f5ed6cf7 Merge branch 'main' into fix/opencode-session-model 2026-08-03 22:56:49 +02:00
Resham Joshi
8fd6aef9be
Merge pull request #879 from getagentseal/fix/mac-menubar-regular-weight
fix(mac): use regular weight for the menubar title text
2026-08-03 13:52:53 -07:00
AgentSeal
6530546f73 fix(mac): use regular weight for the menubar title text
Render the macOS menu-bar title with `.regular` instead of `.medium`, matching
the visual weight of SwiftBar / MeetingBar. The flame symbol keeps its `.medium`
config. Applies the one-line change from #851.

Co-authored-by: Tim De Pauw <timdp@users.noreply.github.com>
2026-08-03 22:52:22 +02:00
Resham Joshi
519f0a064d
Merge pull request #878 from getagentseal/fix/by-activity-straddle-undercount
fix(aggregator): attribute a midnight-straddling turn's By Activity to today on the all-provider view
2026-08-03 13:48:59 -07:00
AgentSeal
dbdbf466a0 fix(aggregator): attribute a straddling turn's category to today on the all-provider view
buildDurablePeriod derived the today slice of the multi-day, all-provider
headline from the unsliced whole-range parse, so a turn spanning local midnight
kept its category and turn count anchored on its yesterday start. The per-call
cost and calls bucketed onto today correctly, but By Activity and the JSON
daily turn count lost the post-midnight half — categories summed to only the
pre-midnight cost while the headline, By Model and By Project were right.

Slice the today parse with filterProjectsByDays first, which re-anchors the
straddling turn to its surviving today calls, so today's category cost lands on
today. Category cost is the sum of the slice's own calls, so day-N + day-N+1
still equals the whole-range total (no over-count); the per-day turn-count
split matches the cache side and the documented per-day semantics.

Adds a regression test in the straddling-turn conservation suite
(mutation-checked: fails on the pre-fix code). Also fills in the CHANGELOG
Unreleased entries for the batch (#853, #856, #872, #846/#859, #866/#867, #833).
2026-08-03 22:46:54 +02:00
ozymandiashh
eb90b29422 fix(menubar): retry transient release-asset download failures
`codeburn menubar` aborted the install on the first bad response from
GitHub release-asset delivery. A transient HTTP 500 on the checksum
fetch (issue #876) killed an otherwise healthy install even though the
asset was published correctly and the next request succeeded.

Retry the zip and checksum downloads up to 3 times with a short
exponential backoff (0.5s, 1s) on 5xx responses and network-level
errors. 4xx is never retried: 404/410 still falls through to the
release-API discovery path unchanged, and a 403/429 rate limit cannot
clear inside the backoff window so it surfaces immediately with its
retry-after hint. The checksum comparison stays outside the retry loop
so a genuine digest mismatch still aborts on the first look.

Thrown errors now name the requested URL so a failure is actionable.
Retry parameters and the fetch/sleep/log seams are injectable, matching
the options pattern in src/sync/push.ts and src/cache-refresh-lock.ts.
2026-08-03 23:08:59 +03:00
Resham Joshi
55bf65ef6a
Merge pull request #859 from avs-io/fix/omp-title-slot-discovery
pi/omp: discover transcripts whose session record follows a title slot
2026-08-03 13:00:17 -07:00
AgentSeal
62f6eb27c7 Merge branch 'main' into fix/omp-title-slot-discovery
# Conflicts:
#	src/providers/pi.ts
2026-08-03 21:59:19 +02:00
Resham Joshi
b8c92bfba7
Merge pull request #846 from jbspeakr/fix/omp-session-discovery
fix(omp): discover title-first session transcripts
2026-08-03 12:45:46 -07:00
AgentSeal
2c65764c0b Merge branch 'main' into fix/omp-session-discovery 2026-08-03 21:44:19 +02:00
Resham Joshi
dc3ea24b1d
Merge pull request #867 from marcreynolds/feat/menubar-degraded-device-indicator
feat(menubar): mark badge when a paired device is unreachable in combined scope
2026-08-03 12:35:17 -07:00
AgentSeal
067174b885 Merge branch 'main' into feat/menubar-degraded-device-indicator 2026-08-03 21:34:54 +02:00
Resham Joshi
bb506e0a3f
Merge pull request #866 from marcreynolds/feat/desktop-combined-scope
fix(dashboard): aggregate paired-device usage in the desktop Dashboard and menubar badge
2026-08-03 12:33:29 -07:00
AgentSeal
ac8ff954cd Merge branch 'main' into feat/desktop-combined-scope 2026-08-03 21:33:03 +02:00
Resham Joshi
458ce1b2d7
Merge pull request #848 from Enclavet/feat/sync-yield-attribution
feat(sync): opt-in git attribution spans on `sync push --attribution`
2026-08-03 12:05:45 -07:00
Resham Joshi
201b289ada
Merge pull request #872 from ozymandiashh/fix/provider-filter-claude-orphan-leak
fix(parser): stop --provider filters from re-surfacing cached claude sessions
2026-08-03 11:54:06 -07:00
Resham Joshi
e05db463a6
Merge pull request #875 from GodCC6/fix/project-filter-test-time-of-day
test: fix time-of-day flake in the durable-headline project-filter tests
2026-08-03 11:42:51 -07:00
chengchuan.zhou
15962202bd test(project-filter): make the durable-headline filter tests time-of-day proof
The tests added in #864 seeded today's live session at a fixed wall-clock
hour (12:00 local). The periods they build end at `new Date()`, and the
suite runs under TZ=UTC, so for any run before 12:00 UTC that timestamp
is in the FUTURE and the range filter correctly drops it. The live half
of the cache/live union then contributes nothing, and the one assertion
that needs a non-zero headline — the unattributed-cost footnote — fell
into renderOverview's "No usage found" early return and went red. Half
of every day was a failing window; a769b50 fixed the start-of-month
flake but this one survived it.

Verified by bisecting the fixture on the unpatched test: moving the
seeded hour from 12:00 to 01:00 (past, at a 03:38 UTC run) turns the
same 12 tests green, so the timestamp's position relative to `now` is
the whole cause.

- Seed the session a few minutes BEFORE now, clamped to today's
  midnight, so it is always both inside today and already in the past.
- Stop the footnote test depending on the live parse at all: seed a
  second, attributable cached day so the headline is non-zero from the
  cache alone. The test now exercises the footnote instead of the
  fixture's timing.
2026-08-03 11:42:32 +08:00
ozymandiashh
6b903a7777 fix(parser): stop --provider filters from re-surfacing cached claude sessions
Claude is scanned via scanProjectDirs instead of parseProviderSources, and
that call had no provider-filter guard. On a --provider <other> run
discoverAllSessions correctly returns no claude sources, so claudeDirs is
empty, but scanProjectDirs still ran: its orphan pass reads the whole cached
claude section and treats every file as no-longer-discovered, re-injecting
PR-bearing entries (and in read-only mode every cached entry) into the result.

The headline stayed correct because it comes from the provider-sliced daily
cache, so only the live-parse panels were wrong. By Model then listed
Anthropic models under --provider cursor while the total showed cursor alone.

Guard the scan with claudeInScope, mirroring the guard the durable-orphan
loop already applies. Deliberately not a claudeDirs.length check: when claude
is in scope but every transcript has been pruned, the orphan pass is what
keeps PR-attributed spend from vanishing.
2026-08-02 19:45:16 +03:00
Andrew Lee
50c8251719 fix(sync): close credential-leak paths; session retraction; span/key/CLI hardening
Review rounds 2-3 + self-review on --attribution:

Credential egress (round 2):
- normalizeRemoteUrl: scp userinfo expressed as an optional regex group
  let backtracking re-parse a credential prefix as host:path
  (x-access-token:ghp_...@host/repo -> token in git.repo). Userinfo is
  now split off at the first @ BEFORE any host matching.
- Positive validation (allow-list) as the final gate on EVERY branch:
  host must be hostname-shaped, every path segment repo-shaped, total
  identity <= 200 chars. Kills transport-helper remotes (ext:: leaks
  local SSH key paths, codecommit:: leaks AWS profile names), residual
  @, spaces/colons, and unbounded strings.
- sanitizePrLinks: links are rebuilt from origin + pathname — userinfo,
  query strings, and fragments are dropped instead of passed through;
  collapsed duplicates dedupe.

Attribution correctness (round 3 + self-review):
- Double-count fix with precise retraction semantics: when a commit
  migrates to a later-parsed tighter-window session, the loser re-emits
  git.commit_count=0. Empty records are emitted ONLY on a true loss in
  THIS computation (lostCandidacy) — a commit that merely aged out of
  the --since range was lost to nobody, and retracting it would
  permanently zero a still-correct server-side count. The sync layer
  additionally requires a prior ledgered state for the session.
- Session dedup key includes project + both window timestamps, so
  ongoing sessions re-emit with corrected span times.
- Span end times clamped like the usage builder (never 0, never
  earlier than start + 1ms).
- CLI mirrors the usage path on attribution push failures instead of
  claiming success.
- Identity normalization: case-insensitive .git strip, doubled path
  slashes collapse.

AI-Origin: human
2026-08-02 12:56:59 +00:00
Andrew Lee
ccee28ae82 fix(sync): address attribution review — cwd-fallback egress, Windows paths, PR-link validation
Review findings on the --attribution PR:

- Privacy: sessions whose project path no longer resolves inherited the
  cwd-fallback repo identity, egressing whatever (possibly confidential)
  repo the user pushes from and falsely attributing its commits.
  buildRepoGroups now tracks per-session identity provenance; the
  attribution path excludes fallback sessions from commit attribution
  entirely (no repo, no commits, PR links only) — they also can no
  longer steal a commit from a genuine session's window.
- Privacy: Windows drive-letter paths (C:/..., C:\..., drive-relative)
  parsed as scp-like remotes, emitting local filesystem paths as repo
  identities. normalizeRemoteUrl rejects drive letters and
  single-character hosts (dotless intranet hosts still accepted).
- Hardening: PR links are shape-checked before sending (https,
  /org/repo/pull/N path, <=256 chars, max 20 per session) — upstream
  parsers only truthiness-check them.
- Safety valve: MAX_ATTRIBUTION_PER_PUSH (10k) caps a first
  --since all --attribution push; dry-run reports the cap.
- Tests: adversarial normalize corpus, cwd-fallback egress repro,
  commit-stealing prevention, PR-link sanitization, and CLI-level tests
  (mock IdP + collector): dry-run sends nothing to the traces endpoint,
  flag-off emits no attribution span names on the wire.
- Docs: reconciled the 'never sent' wording with reality (PR links ride
  even when repo is null; device_id/methodology/timestamps disclosed).
  CHANGELOG Unreleased entry added.

AI-Origin: human
2026-08-02 12:29:18 +00:00
Andrew Lee
1bf7206842 feat(sync): push git attribution spans with --attribution
Expose the yield session-to-commit correlation through codeburn sync so
backends can join AI usage to git activity without local git hooks.

- yield: export normalizeRemoteUrl (host/org/repo; credentials, ports,
  and .git stripped) and computeAttributionRecords, which reuses the
  exact repo-grouping + tightest-window attribution from computeYield
  (extracted into a shared buildRepoGroups) and joins in the normalized
  origin remote and session prLinks.
- otlp: two new span types sharing the session traceId —
  codeburn.session.attribution (git.repo, git.pr_links, git.commit_count)
  and codeburn.commit (git.sha, git.in_main, git.was_reverted). Resource
  attribute codeburn.attribution_methodology=timestamp-window marks the
  attribution as inferred.
- push: generic send core reused by usage and attribution batches. Dedup
  keys encode mutable state (inMain/wasReverted), so a state transition
  re-sends the updated fact while identical states dedupe via the
  existing sent-ledger.
- cli: opt-in --attribution flag on sync push (dry-run aware); commits
  in repos with no network remote are never sent.

AI-Origin: human
2026-08-02 12:28:13 +00:00
Resham Joshi
2de4d100bf
Merge pull request #864 from GodCC6/fix/project-filter-durable-headline
Some checks failed
CI / semgrep (push) Has been cancelled
fix: --project/--exclude are ignored by the durable headline totals
2026-08-01 16:19:03 -07:00
AgentSeal
3432a07bec Merge branch 'main' into fix/project-filter-durable-headline 2026-08-02 01:18:13 +02:00
AgentSeal
a769b5008a fix(daily-cache): keep prototype-named project keys; make filter tests date-deterministic
Two issues on top of the --project/--exclude durable-headline fix:

- sanitizeProjects dropped any project whose key is an Object.prototype member
  name (constructor, valueOf, __proto__, ...). A project key is a directory
  basename, so such a name is legitimate, and dropping it left the day's
  per-project split summing to less than the day cost — so the sliced,
  project-filtered headline silently lost that project's spend with no footnote.
  The keys are written via setOwn (defineProperty), so keeping them is
  pollution-safe; only the redundant `name in Object.prototype` guard is removed.
  Regression test added (mutation-checked: fails without the guard removed).

- The new project-filter tests seeded a carried day 10 days ago but ranged over
  the calendar month, so within the first 10 days of a month that day fell out
  of range and the tests went red. Replaced with a fixed 20-day window that
  always spans the seeded day.
2026-08-02 01:16:35 +02:00
Resham Joshi
b94d0ac67c
Merge pull request #853 from KENSHI601/fix/turn-range-per-call-filter
fix(parser): range-filter calls inside turns instead of dropping day-spanning turns
2026-08-01 15:37:24 -07:00
AgentSeal
6c411ad96d Merge branch 'main' into fix/turn-range-per-call-filter 2026-08-02 00:36:34 +02:00
AgentSeal
5a12dccc70 fix(parser): classify range-sliced turns from the whole turn on the provider path
The Codex/OTel/network provider path classified a date-sliced turn from only
its in-range calls (cachedTurnToClassified(slicedTurn)), while the Claude path
kept the full-turn classification. So a midnight-straddling turn was
categorized and edit-counted differently by provider — a Read at 23:59 plus an
Edit at 00:05, sliced to the 23:59 side, read as coding/hasEdits on Claude but
exploration/no-edit on Codex. This contradicted the documented intent that
category/hasEdits/retries are whole-exchange judgments, not per-call sums.

Classify the full turn, then trim its calls to the range (mirroring the Claude
path's classifiedTurnSlicedToRange). Cost/calls still come from the retained
calls, so conservation is unchanged; only the turn-level judgments are now
provider-independent.
2026-08-02 00:35:30 +02:00
Resham Joshi
39441de8b2
Merge pull request #856 from avs-io/fix/daily-cache-degraded-completeness
daily-cache: never finalize history against a degraded session parse
2026-08-01 14:10:17 -07:00
AgentSeal
1579eb0899 Merge branch 'main' into fix/daily-cache-degraded-completeness 2026-08-01 23:09:27 +02:00
AgentSeal
90dffcddc2 fix(daily-cache): trust a stamped watermark so an idle tail is not re-derived every launch
The watermark pull-back could not tell a legitimately-finalized idle tail
(recent days had no activity, so they are absent from the cache) from the
corrupt cache it heals (a degraded parse finalized past days it never read).
Both look like lastComputedDate > newest populated day, so an idle user
re-parsed the tail on every launch, escalating to a full re-derive under
sustained lock contention where before it did nothing.

A degraded parse can no longer set complete, so the corrupt state can only
come from pre-fix code. Stamp watermarkTrusted whenever a COMPLETE parse
finalizes, and pull the watermark back only for unstamped caches. Pre-fix
caches heal once, then are trusted; caches the fixed code writes are trusted
from the first finalize. The heal still recovers genuinely missing days.
2026-08-01 23:07:22 +02:00
Resham Joshi
fee91be92b
Merge pull request #833 from rbstp/chatgpt-enterprise-support
feat(codex): show the credit limit on credit-metered ChatGPT workspaces
2026-08-01 13:38:00 -07:00
AgentSeal
53ecb3635c Merge branch 'main' into chatgpt-enterprise-support 2026-08-01 22:35:02 +02:00
AgentSeal
75a02854fd fix(codex): align credit/dollar footer formatting across desktop and menubar
The desktop dollar-balance footer used toFixed, which drops thousands
separators, while the new menubar footer groups via a currency
NumberFormatter, so the two clients showed $12500.00 vs $12,500.00 for
the same balance. The menubar credit footer also rounded half-even while
the desktop uses Math.round (half-up), disagreeing on exact-half values.

Render the desktop dollar path through the en_US currency locale, and pin
the menubar footer formatter to half-up. Adds a grouping assertion (TS)
and an exact-half rounding test (Swift).
2026-08-01 22:32:53 +02:00
Resham Joshi
807b1f422f
Merge pull request #855 from avs-io/fix/refresh-lock-malformed-recovery
Some checks are pending
CI / semgrep (push) Waiting to run
cache: recover a corrupt session-refresh lock instead of freezing ingestion
2026-08-01 13:05:39 -07:00
iamtoruk
e7576e7fa5 Trim recipient banner to badge bounds
Some checks are pending
CI / semgrep (push) Waiting to run
2026-07-31 17:23:32 -07:00
iamtoruk
fd1e02319c Replace recipient badge with Codex + Claude banner 2026-07-31 17:20:08 -07:00
Marc Reynolds
f19d9d8d4b
feat(menubar): mark badge when a paired device is unreachable in combined scope
Under combined scope the badge falls back to the local figure whenever a
paired device doesn't report (asleep / off-network), which read as a glitch.
Append a dimmed "reachable/total" marker (e.g. "$95.94 /mo · 1/2") and a
matching tooltip so the reduced total is legibly "peer unreachable" instead.
The marker clears the instant every paired device reports again.

Adds AppStore.menubarBadgeDeviceShortfall plus tests.
2026-07-31 15:13:38 -06:00
Marc Reynolds
61581edb18
feat(dashboard): add combined-device scope to desktop app and menubar badge
Fixes #795: the desktop Dashboard showed only local-device cost while the
menubar and web GUI aggregated across paired devices.

Desktop app:
- Add a Local/Combined Scope setting (Settings > General), mirroring the
  macOS menubar. Combined passes `--scope combined` to the CLI, forces the
  provider filter to all, and persists to localStorage.
- Overview hero shows the paired-device aggregate (cost/calls/sessions) with
  a "Combined · N devices" caption and per-device breakdown when Combined is
  selected; detailed panels remain local (the combined payload carries totals
  only).

Menubar:
- The badge figure now reflects Combined scope instead of always showing the
  local total: refreshMenubarBadge fetches the combined payload for the badge
  period and the badge renders the cross-device aggregate, falling back to
  local when no combined payload is available.
2026-07-31 15:12:29 -06:00
chengchuan.zhou
4bfcc020f6 fix(aggregator): apply --project/--exclude to the durable headline
The durable headline unions the carry-forward daily cache with today's
live parse. Cached days were sliced to the requested provider
(sliceDayToProvider) but never to the requested project, so a
--project/--exclude query counted every historical day WHOLE while the
detail panels — By Project / By Activity / By Model, all built from the
name-filtered live parse — left the filtered projects out. The Overview
total and the panels below it could not be reconciled; on a real
one-week corpus the headline ran $2,428.85 over the By Project sum,
which is exactly the excluded projects' spend.

Add sliceDayToProject, the project-level counterpart of
sliceDayToProvider, and apply it to the cache-sourced days. Cost, calls,
sessions and savings come out of the per-project day stats the cache has
carried since v15, so they stay exact and still include days whose
session files have expired. Provider slices carry their own project
split, so --provider on top of a project filter stays consistent.

Fields the cache has no per-project split for are handled explicitly
rather than reported as the filtered projects' own:

- tokens/models/categories come from the (project-filtered) live parse
  when a project filter is active, which is exact for every surviving
  session and matches the panels that read the same parse;
- days, or provider slices, carried from before v15 have no project
  split at all and cannot be attributed, so they contribute nothing and
  their cost is returned as unattributedCostUSD and footnoted by the
  terminal overview instead of being folded into a filtered total.
2026-07-31 19:32:02 +08:00
KENSHI601
7591e68851 fix(parser): make day-spanning turn slices conservation-correct
Extends the per-call range filter so multi-day periods stop losing
usage (review on #857):

- Re-anchor a sliced turn's timestamp to its first surviving call in
  parseProviderSources, so every turn slicer shares one split rule.
- scanProjectDirs (Claude Code path): slice per call instead of
  dropping the whole turn on its first assistant timestamp; category/
  subCategory/retries/hasEdits stay classified from the full turn.
- aggregateProjectsIntoDays: bucket cost/calls/tokens (and the model,
  project, provider-slice rollups built from them) under each call's
  own day; turn-level stats (categories, editTurns, oneShotTurns) stay
  turn-anchored. This is the conservation fix: cache (<= yesterday) +
  live (today) unions now sum to the whole range for straddling turns.
- buildJsonReport's dailyMap fallback follows the same per-call rule
  so the no-durable path can't diverge from durable.days.
- filterProjectsByDateRange (dashboard) and filterProjectsByDays
  (menubar/history) slice per call instead of dropping whole turns.

Adds the straddling-turn buildDurablePeriod case to the durable-totals
parity suite (day-N + day-N+1 == whole-range calls/cost/tokens,
verified to fail without the fix), covers the today view and the
surface filters, and makes the suite hermetic on machines with real
provider data.
2026-07-31 00:37:20 +08:00
Aditya Vikram Singh
d5144593f3 fix(cache): recover a corrupt session-refresh lock instead of freezing ingestion
observe() classified a stable unparseable session-refresh.lock body as the
terminal 'unavailable'. parseAllSessions routes that to a read-only parse, so a
zero-byte or truncated lock froze warm-cache ingestion permanently across every
later run while each command still exited successfully.

A corrupt body is now a recoverable observation carrying a real mtime, and is
recovered only through the UNMODIFIED staleness gate — tryTakeover and the age
check are byte-identical to main. sameObservation gains an explicit null/non-null
boundary and a sha1 of the raw bytes, because two corrupt bodies have no tokens
to compare and mtime granularity is coarse on some filesystems.

The heartbeat deliberately does NOT rewrite a body it cannot prove is its own.
An owner that cannot prove ownership ends its ownership: mtime stops advancing,
the publication fence refuses, and a successor recovers the lock one staleMs
later. Losing that parse is the price of never having two owners.
2026-07-30 13:06:25 +05:30
Aditya Vikram Singh
213f4cbe0e fix(daily-cache): never finalize daily history off a degraded parse
A read-only session parse (served when the cache refresh lock times out or
is unavailable) reported itself as a complete hydration, so the daily
backfill published `complete: true` and advanced `lastComputedDate` to
yesterday over days the parse never covered. Because gapStart is
lastComputedDate + 1, those days were never looked at again — observed as a
cache marked complete at 2026-07-28 whose newest entry was 2026-07-25.

- parser: a read-only run reports a complete hydration only when nothing
  changed under the snapshot it served (a skipped or staled file makes it
  partial).
- daily-cache: only a complete parse may advance `lastComputedDate`, on both
  the gap and the full re-derive paths.
- daily-cache: a cache whose watermark outruns its newest populated day has
  its watermark pulled back to that day, so the ordinary gap parse re-derives
  the tail instead of trusting the marker.

Days are only ever added or re-derived, never dropped; the preservation bias
is unchanged and covered by test.
2026-07-29 17:37:44 -07:00
Resham Joshi
146037bfd5
Merge pull request #805 from ihearttokyo/agent/fix-daily-history-scan
Some checks failed
CI / semgrep (push) Has been cancelled
feat(codex): add tool-excluded active Tok/s metrics
2026-07-29 17:30:04 -07:00
Aditya Vikram Singh
47981a50ce fix(dashboard): label the day-count denominator, explain durable carry-forward
Issue #767 item 3 reports two different active-day counts on one dashboard
screen: the Daily Activity panel shows "of 37" (from a bounded live scan of
surviving session files) while the same period's headline can reflect more
days via the durable daily cache, which also counts days whose session files
have since expired. Each count is correct for what it measures - the panel
intentionally scans a fixed six-month window independent of the selected
period tab so scrolling always works, while the headline is scoped to the
active period tab.

Re-deriving the panel's count from the durable series was considered and
rejected: it would need a second buildDurablePeriod call scoped to six months
independent of the period tab, which is a behavior and perf change, not
polish. Kept the count unchanged and instead labelled the denominator -
dailyActivityFooter (src/dashboard.tsx) now renders "of N days scanned"
instead of a bare "of N", so the panel reads as "here's what the live scan
covered" rather than as a contradiction of the headline. Checked the
Optimize view's similar "Showing X-Y of Z" footer (line ~794): it counts
findings, not days, and isn't part of this ambiguity.

Also fixes an adjacent gap found while investigating: overview.ts's
non-interactive report already had a footnote for durable-cache carry-forward
("includes $X preserved from expired session logs") when carriedCostUSD > 0;
the interactive dashboard's Overview panel had no equivalent, so a headline
that included carried-forward cost had no explanation anywhere on screen.
Extracted the wording as carriedCostNote (format.ts) and surfaced
carriedCostUSD through DurableOverview so the TUI shows the same footnote.
This is separate from the active-day-count fix above - it explains cost
carry-forward, not day counts.

Fixes getagentseal/codeburn#767 (item 3).
2026-07-29 23:02:05 +05:30
Aditya Vikram Singh
7a200915b4 fix(web): accept a session id prefix in /api/context/tree
`codeburn context <session>` accepts an 8-char session id prefix (findClaudeSession/
findCodexSession resolve it via startsWith), but /api/context/tree additionally
required the resolved ref's sessionId to equal the id passed in verbatim - so
the same prefix that works on the CLI 404s through the API.

findClaudeSession/findCodexSession are already the single source of truth for
prefix resolution used by the CLI; the API now trusts that resolution instead
of re-validating for an exact match afterwards, so both surfaces behave the
same way for a prefix.

Fixes getagentseal/codeburn#767 (item 2).
2026-07-29 22:58:10 +05:30