cctally 1.99.0 → 1.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,57 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.100.0] - 2026-08-18
9
+
10
+ ### Added
11
+ - Release preflight now has a free local Linux matrix that runs full pytest plus every Linux-relevant private harness as a non-root user on Python 3.11, 3.12, and 3.13 before a cut, closing the interpreter and filesystem coverage gap that previously surfaced only after publication; the sole recorded omission is the macOS/Homebrew LAN-runner harness already owned by Gate 0. Only the fixed no-argument matrix can pass the release gate, and it rechecks the candidate HEAD and clean tree after its final lane so a multi-hour run cannot certify a checkout that changed underneath it (#595).
12
+ - `cctally dashboard-perf` reports what a tick of the running dashboard actually costs, without restarting it. Restarting to answer that question does not work — it discards both the accumulated memos and the condition being diagnosed — so the counters it reads are always on. It states the publish period separately for Codex-active and Codex-idle ticks, because those are different populations: an idle tick reuses the whole Codex source bundle and never runs the Codex leg, so a single averaged period describes neither. Each regime reports its sample count, the median and the observed range, and a regime with no samples says `no samples yet` rather than showing a zero, so you can tell "measured and fast" apart from "not measured". Alongside that it reports the tick-cost breakdown with the ingest and builder halves named and mutually exclusive, the dispatch mix, and any silent Group A cache-open failures — a failure class that until now fell back to a slower query with byte-identical output and told nobody. `--trace on` and `--trace off` arm and disarm the deep phase trace on the running process; the change takes effect at the next authoritative build, and the report states the requested and the applied state separately so `off` cannot appear to succeed while tracing is still on. `--json` emits a stamped envelope whose `diagnostic` block passes the server payload through verbatim and stays explicitly opaque. The command reaches only an IP-literal loopback address and never a LAN one (#583).
13
+ - Clicking the dashboard's sync chip while a rebuild is already running now queues your refresh instead of refusing it. That click previously returned `503` and was dropped without telling you, so pressing it during a slow rebuild simply did nothing. The request is now folded into the next rebuild, and the dashboard reports it back: you see the refresh queued, then running, then settled, together with any warnings it produced. Because the same reporting covers ordinary background rebuilds, a dashboard that is merely busy no longer looks identical to one that has wedged (#583).
14
+ - `cctally doctor` now reports the size of the transcript store's write-ahead log, and `cctally db checkpoint --db conversations` drains it. It already reported the same thing for `cache.db`, and `conversations.db` carries the same 128 MiB cap, so an oversized log there was equally a sign that the defenses had not contained it and equally invisible. The check warns above 256 MiB and names the new command, which was not previously available for that store (#583).
15
+ - `CCTALLY_DASHBOARD_API_TOKEN` lets a `record-usage` nudge authenticate against a dashboard started with LAN access and a bearer token. Such a nudge was rejected and silently discarded before this release as well; because the dashboard mints its token per process and never writes it to disk, exporting this variable is currently the only way to close that gap (#583).
16
+
17
+ ### Changed
18
+ - Dashboard update publications now cache complete SSE frame bytes once per privacy/config variant, so additional connected tabs no longer repeat UTF-8 encoding of the same multi-megabyte frame; each connection still keeps its own stateful gzip stream (#609).
19
+ - A Codex dashboard with more than one account no longer costs extra database work per account on every rebuild. Each account card ran its own accounting query and then filtered the whole loaded population again, so both the queries and the work grew with the number of accounts you have. The cards are now derived from the data the rebuild has already loaded, which it walks once however many accounts there are. The dashboard also reuses one of its Codex quota reads between rebuilds. The read that correlates a five-hour block against its weekly cycle is retained and re-issued only when the quota evidence, the account registry or the account decoration changes. The dashboard's own bounded quota read is still issued on every rebuild, because what it returns depends on the instant it is asked. Nothing published changes — the dashboard's payload is byte-for-byte what it was (#583).
20
+ - The dashboard's health check no longer re-reads your whole Codex quota history every time it runs. The dashboard embeds a doctor summary and recomputes it every 30 seconds, and computing it scanned every quota observation ever retained in order to find the most recent one per identity — 277,207 rows to return 16 of them on the store this was measured against, which took 1.2 seconds and was about two thirds of the whole health check. Those rows are now kept and read again only when the quota evidence, the attribution or the database file changes. Everything the check derives from the clock is still recomputed on every run: how fresh your quota data is, its age in seconds and whether it counts as stale all move with time alone, so freezing them would freeze a health verdict while the check time shown beside it kept advancing. Nothing the check reports changes; it costs less to produce. Note when the saving applies. cctally reuses the rows only while no new Codex rate-limit observation arrives, and Codex records one whenever you use it, so the reuse holds between Codex sessions and not during one. The figures below were measured against a copy of a real store, where nothing new arrives by construction: the health check fell from 1.7 seconds to 0.41, and its share of a dashboard rebuild from 29% to 9% (#583).
21
+ - The dashboard's transcript sync no longer takes an unbounded share of a core as your transcript store grows. That thread read new conversation data and then slept for a fixed interval, so once a pass cost more than the interval it spent most of every cycle working — measured at about 86% of a core against a large store. It now rests at least as long as the pass it just finished, exactly as the main rebuild loop has for some time, which holds it at or below half a core however large the store gets. On a store big enough for that to bind, new turns reach the conversations list up to about twice as late as before; an open conversation is unaffected unless you have turned `dashboard.live_tail` off, in which case it follows the same slower cadence. `cctally dashboard-perf` reports this loop's measured cost and its share of a core (#583).
22
+ - The dashboard sends far less data on every update, and compresses what it does send. Each update carried the whole state, and each provider's data was in it twice — once on that provider's own entry and once again under the `All` view — so a tab left open re-downloaded and re-parsed both copies every few seconds. The duplicate is gone, and the stream and `/api/data` now negotiate gzip, which a browser does automatically and `curl --compressed` opts into. Together those cut one update on the project's own test corpus from 305,466 bytes to 26,155. A client that does not ask for compression still receives exactly the JSON it received before. If you read `sources.all.data.providers` from `/api/data` yourself, read `sources.claude.data` and `sources.codex.data` instead — the key is still present but both of its members are now null, and the wire version is 10 (#583).
23
+ - A dashboard tab you are not looking at stops costing anything. After thirty seconds hidden, the tab disconnects its update stream and reconnects when you come back, so a dashboard left open in a background tab no longer downloads and parses the whole state every few seconds. Switching away and back inside those thirty seconds does not reconnect at all. The conversation viewer's live-tail and the update-progress stream are unaffected and keep running while the tab is hidden — an upgrade started in one tab still finishes if you switch to another. One visible change on return: an alert that fired while the tab was hidden appears in the alerts panel but does not raise a toast (#583).
24
+ - Loading the dashboard now transfers the state once instead of twice. The page fetched the whole envelope and then immediately received the same envelope again as the first stream frame; it now waits for that first frame, which halves what a cold load downloads and parses (#583).
25
+
26
+ ### Fixed
27
+ - `/api/doctor` and the block-detail route no longer append a second HTTP response when a body write fails after the first response is committed; preparation failures still return their existing 500 response (#610).
28
+ - The doctor summary embedded in `/api/data` now refreshes when the dashboard update thread changes `update-state.json`, so it agrees immediately with the live `/api/doctor` report under both frozen and normal sync. The refresh patches only its derived fields onto the latest held snapshot, so a concurrent full sync cannot be overwritten by an older partial snapshot (#594).
29
+ - A dashboard refresh refused because another rebuild is still holding the sync lock now reports `refresh busy` in a calm informational state instead of silently returning to ordinary freshness text (#602).
30
+ - The UI-QA commit guard now lets any staged dashboard source or test change skip browser certification only after a clean staged rebuild proves the served bundle is byte-identical; bundle-changing, static-output, and failed-build cases remain gated (#601).
31
+ - The dashboard envelope fixture generator now refuses to overwrite an existing fixture when that would remove top-level keys unless the caller explicitly opts into the loss (#603).
32
+ - Failed remote test runs whose sanitized evidence is refused or wholly redacted now print an opt-in command for retrieving that run's private raw logs, and pytest failures with no identifiable test node are reported as infrastructure failures instead of product failures (#604).
33
+ - `GET /api/data` and the dashboard's live stream no longer disagree about whether a rebuild is still filling in data. The endpoint read the dashboard's current snapshot while the stream sends what was last published, and those are not the same thing during a first-run or long-gap sync, so a script polling `/api/data` could be told the data was complete while every open tab was still showing it fill in — observed at 2.6 seconds into a build that finished at 105.6. `/api/data` now serves the most recently published state, which is by definition what the stream last sent (#600).
34
+ - A `/api/data` response that failed part-way through sending no longer appends a second HTTP response onto the connection the client is already reading (#583).
35
+ - The dashboard now keeps four-figure Claude, Codex, and combined spend headlines fully readable at 320px instead of silently dropping trailing digits, and the desktop Daily heatmap shows its totals footer in full at the default scroll position instead of cutting the last row inside its own pane (#593, #596).
36
+ - The Codex dashboard now skips future-start weekly quota cycles instead of letting one anomalous reset fail the whole provider source and log the same error on every refresh (#599).
37
+ - The dashboard's phone hero now keeps both Claude quota metrics inside the 320px usage zone, presents decorated account cards as a full-width horizontal rail instead of a 672px stack, and preserves `Unavailable` or `Partial` in compact source-status warnings instead of reducing them to domain names (#561, #562, #563).
38
+ - The dashboard's `All` view now shows a focused Claude account's own week-to-date spend beside its card, including `$0.00`, instead of saying `no data` (#588).
39
+ - A focused Codex account without its own budget now points at the per-account `budget.codex.accounts` configuration and names that account's immutable key. The card no longer offers the vendor-wide `cctally budget set … --vendor codex` command, which changes a different budget axis and could never update the focused card (#586).
40
+ - Settings filtering now announces only the settled result count instead of every intermediate keystroke; the overlay also drops an unreachable post-save notice and a no-op row class, while immediate reopen after saving remains clean (#557).
41
+ - The dashboard's Forecast range summaries and all-provider Cache Report composition now expose their existing accessible names as semantic groups instead of generic containers (#578).
42
+ - The 5-hour block navigator in the Current Usage modal now stays mounted while a lazily requested block loads, so the control no longer disappears under the pointer. The prior block table is replaced by an explicit loading status until the requested detail arrives, on both Claude and Codex (#577).
43
+ - The dashboard's Codex Trend panel and modal now describe quota history as cycles throughout, including table headings, relative labels, comparison copy, charts, and assistive text, while Claude retains week vocabulary (#576).
44
+ - The dashboard's decorated Codex spend headline is no longer clipped mid-glyph at 320px; the single-account mobile hero remains unchanged (#590).
45
+ - The Codex dashboard no longer gains horizontal page overflow when its narrow scrolled header includes an account selector; clean 480px-and-wider headers keep the selector (#587).
46
+ - The dashboard's `All` view now shows a focused Codex account's own cycle-to-date spend beside its card instead of falling back to the unavailable combined figure (#589).
47
+ - Arming the dashboard's phase trace no longer switches progressive first paint off. A first-run or long-gap sync publishes partial frames as it fills, and every one of those was suppressed while a trace was active — so the very run you were diagnosing behaved differently from the one you were trying to explain. The hazard that justified the suppression was real but narrower than a blanket skip, and the nested build is now isolated from the enclosing trace instead (#583).
48
+ - The dashboard's data-age figure no longer claims a freshness it does not have. A rebuild that failed, or that degraded and retried, still stamped a fresh sync time, so the age shown described an attempt rather than a successful validation of your data. A failed or degraded tick now reports no sync time at all, and the error state already on screen explains why (#583).
49
+ - A dashboard rebuild blocked by a locked `cache.db` now says the cache database is busy rather than reporting a generic failure, so a transient lock during concurrent hook activity reads differently from genuine cache corruption (#583).
50
+ - `cctally dashboard --sync-interval` is now documented as what it actually is. It sets a floor on the cooldown between rebuilds rather than the period between them: the loop always rests at least as long as the rebuild it just finished, which holds its CPU use at or below half a core however large your store grows. The documentation previously described it as a plain cadence in seconds, which is only true while rebuilds are fast (#583).
51
+
52
+ ## [1.99.1] - 2026-08-15
53
+
54
+ ### Fixed
55
+ - The dashboard now recognizes the previous calendar day as `Yesterday` across a fall-back daylight-saving transition instead of falling through to the absolute timestamp (#584).
56
+ - Budget figures no longer depend on which Python version is running cctally. The dashboard's Claude and Codex budget blocks added their spend with the built-in `sum()`, which CPython changed in 3.12 to use compensated summation, so the same spend over the same entries produced `$49.20424485` on Python 3.12 and later and `$49.204244850000016` on Python 3.11. The rendered figure is rounded to cents and never differed to the eye, but the published payload did, which is why the repository's own byte-comparison of that payload failed on 3.11. These four sums now use the exactly-rounded helper the rest of the cost surfaces already use, so every supported interpreter publishes the same number.
57
+ - `cctally account attribute --help` no longer prints `(default: None)` beneath `--since`, which is a required argument and therefore has no default. Python 3.13 already suppressed it and 3.11 and 3.12 did not, so the same command printed two different help texts depending on the interpreter. cctally now applies the 3.13 rule on every supported version, and this covers every required option on every subcommand, not only this one.
58
+
8
59
  ## [1.99.0] - 2026-08-15
9
60
 
10
61
  ### Added
@@ -18,6 +69,7 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
18
69
  - Complete the Claude/Codex parity certification with current source-aware dashboard, analytics, sharing, conversation, privacy, and scale evidence; the TUI remains explicitly Claude-only under its bugfix-only maintenance policy (#322).
19
70
 
20
71
  ### Fixed
72
+ - Failed authoritative test-gate records now retain the exact `test-remote` case function and source line even when private assertion detail is redacted, and the local record survives the next passing invocation (#579).
21
73
  - Schema tables, views, triggers and indexes in both cache stores now have a structural delivery guard, so a definition added without a migration cannot silently reach only fresh installations (#580).
22
74
  - Claude-active dashboard refreshes now incrementally update the `All` view's shared-range project and daily aggregates instead of rescanning and repricing the full thirty-day Claude history on every new entry. On the 60,000-entry benchmark with one live append before each warm run, the median added source-build time fell from about 404 ms to 2.5 ms while project and daily totals remained byte-identical to a cold rebuild (#567).
23
75
  - A Codex-active dashboard refresh now updates only changed rollout paths, accounts, and day/period/session/project aggregates instead of re-reading and re-aggregating the complete retained Codex history. On a copied production store with 154,600 Codex entries, a caught-up live dirty iteration including ingest completed in 4.77 seconds (9.77-second cooldown-adjusted publish period) while preserving the exact prior dashboard payload (#582).