cctally 1.10.2 → 1.11.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 +22 -0
- package/bin/_cctally_cache_report.py +938 -0
- package/bin/_cctally_dashboard.py +619 -6
- package/bin/_cctally_tui.py +45 -0
- package/bin/_lib_blocks.py +4 -0
- package/bin/_lib_render.py +5 -4
- package/bin/cctally +102 -386
- package/dashboard/static/assets/index-BJ16SzRL.js +18 -0
- package/dashboard/static/assets/index-C1xH9GBW.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +2 -1
- package/dashboard/static/assets/index-Cy59E7Ru.js +0 -18
- package/dashboard/static/assets/index-Dp14ELVt.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.11.0] - 2026-05-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Dashboard: new Cache Report panel and modal exposing the `cctally cache-report` anomaly + savings surface as an always-on watchdog — accent-teal border by default, flips to accent-amber when today crosses an anomaly trigger (`cache_drop` ≥ 15pp drop vs the 14-day median or `net_negative` (`net_usd < 0`)), with a 14-day cache hit % mini-sparkline and a 7-day "+$X saved · N ⚠ days" subline; click the panel for a modal with six sections in order — today's spotlight (status pill + inline stat row + reasons line on anomaly), the full 14-day cache hit % timeline (`CacheSparkline` large variant with a ±5pp tinted baseline band), per-day net $ stacked bars (saved-green + thin red wasted-segment on positive days, downward amber bar on net-negative days), a counterfactual callout ("without caching, you'd have paid +$X more · cache efficiency Y%"), the daily rows table with per-column header colorization (date neutral · cache % cyan · saved green · wasted red · net purple · flag amber), and by-project (magenta) + by-model (blue) breakdown sub-cards. Panel sits at the tail of `DEFAULT_PANEL_ORDER` (position 11) on fresh installs; existing users with a saved `panelOrder` get the panel appended by the existing `reconcilePanelOrder` migration. No new third-party dependencies (hand-rolled SVG for the timeline + bar chart). Envelope is additive + nullable on the `cache_report` field — `envelope_version` stays at 2.
|
|
12
|
+
- Dashboard: `POST /api/settings` now accepts a `cache_report` block with key `anomaly_threshold_pp` (integer 1-100, default 15) — the modal's gear-icon settings popover round-trips through this endpoint and re-evaluates anomalies on the next sync. Invalid values return HTTP 400 with `{error, field}` (matching the existing settings-block validator convention); the popover surfaces the error inline under the input. The CLI's `cctally cache-report --anomaly-threshold-pp` flag is independent in v1 and continues to use its own default; the new `cache_report.anomaly_threshold_pp` config applies only to the dashboard sync path.
|
|
13
|
+
- CSS: new `--accent-teal` design token (the 11th panel accent color) plus `.panel.accent-teal` rule following the same shape as the other nine accent variants, and a `.crm-*` namespace for the Cache Report modal chrome (spotlight sub-card, chart frames, counterfactual callout, daily-rows table with per-column header accents, breakdown sub-cards, settings popover). Pickup follow-up: the panel's mobile collapse (`< 720px`) is now driven by a `useIsMobile()` hook applying the `cache-report-collapsed` modifier class (Implementor B added the CSS rule but left the JS toggle unwired).
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- CLI: `cctally cache-report` now buckets daily rows by the resolved `display.tz` instead of host-local. The pre-existing aggregator at `bin/cctally:2311` correctly resolved `display.tz` for `--since`/`--until` parsing but did NOT pass it to the day-bucketing path, so `--tz America/Los_Angeles` (or any non-host tz) shifted window edges without shifting the day-bucket dates rendered in the table. Threading an explicit `display_tz: ZoneInfo | None` parameter through `_aggregate_cache_by_day` closes the gap. Host-local hosts (the harness convention `TZ=Etc/UTC`) see no goldens change; non-UTC hosts may see day-bucket date strings shift by a day at midnight boundaries. Goldens regenerated via `bin/build-cache-report-fixtures.py`; aggregate per-day USD values are unchanged.
|
|
17
|
+
- HelpOverlay: panels at position 11+ now render an em-dash for the keybind cell instead of a literal `'11'` `<kbd>` label. The digit hotkeys at `main.tsx:62-74` only route to positions 1-10 of the active `panelOrder`; rendering `String(idx + 1)` for `idx > 8` previously emitted a `'11'` chip that wasn't bound to anything. Positional rule, not panel-id-specific — whatever panel sits at position 11 (Cache Report by default; any panel after drag-reorder) renders the em-dash.
|
|
18
|
+
- Dashboard Cache Report: panel chrome (border accent, header text color, "⚠ Today" header badge, sparkline today-marker color) no longer flips to amber during the baseline-building window (first 1–4 captured days). The server-side classifier can fire `net_negative` without a baseline, so `cr.today.anomaly_triggered` arrives true while `baseline_daily_row_count < CACHE_REPORT_MIN_BASELINE_DAYS` (5), but the watchdog is supposed to stay neutral until the 5-day floor exists. A new `chromeAmber = anomaly_triggered && !insufficient` gate at `CacheReportPanel.tsx` decouples the panel chrome from the headline copy — the modal's `.modal-card` accent mirrors the same gate so the panel-to-modal handoff stays teal during baseline-building. Round-2 Codex review finding (issue #77).
|
|
19
|
+
- Dashboard Cache Report: by-project / by-model breakdown cards now aggregate only over the same calendar dates as the displayed 14-day table. The kernel's rolling window can emit 15 distinct display-tz buckets when `now_utc - 14d` and `now_utc` straddle midnight (any non-UTC `display.tz`); `days` was already sliced to `window_days` in `bin/_cctally_dashboard.py`, but the breakdown inputs (raw `entries` for by-project; `result.rows` for by-model) were not — the cards silently included the dropped oldest bucket and stopped reconciling against the visible table / `CacheNetBars`. A `kept_dates = {r.date for r in days}` filter on both sides closes the gap; regression at `tests/test_dashboard_envelope_cache_report.py::test_build_cache_report_snapshot_breakdowns_match_days_window`. Round-2 Codex review finding (issue #77).
|
|
20
|
+
- Dashboard Cache Report modal: the daily-rows table's "Cache %" cell color now tracks each row's own `anomaly_reasons` instead of recomputing every row against today's `baseline_median_percent`. The server-side per-row classifier (`_cctally_cache_report._classify_anomalies`) uses `exclude_row=row` so each row has its own baseline; a window with five early 80% days followed by many 50% days kept the first 50% rows flagged `cache_drop` against the 80% reference, but the previous predicate (`d.cache_hit_percent < today.baseline_median_percent - CACHE_REPORT_BAND_PP`) painted them `hit-good` once today's median also drifted to 50% — green cells next to a ⚠ Flag column on the same row. Switching to `d.anomaly_reasons.includes('cache_drop')` keeps the cell color and the Flag column in lock-step regardless of how today's baseline moves; the `baselineKnown` neutral guard for the cold-start window is preserved. Round-2 Codex review finding (issue #77).
|
|
21
|
+
- Dashboard Cache Report modal: spotlight pill, sub-card border, and reasons line no longer flip to amber `⚠ Anomaly` during the baseline-building window (first 1–4 captured days). The server-side classifier fires `net_negative` without a baseline so `cr.today.anomaly_triggered` arrives true while `baseline_daily_row_count < CACHE_REPORT_MIN_BASELINE_DAYS=5`, but the panel and the outer modal-card chrome already gate the amber flip behind `!insufficient`. `CacheReportSpotlight` checked `anomaly_triggered` before `insufficient`, so the spotlight contradicted the panel and modal-card by rendering ⚠ Anomaly on the same day the rest of the surface said "Building baseline". The pill precedence now puts `insufficient` first, mirroring the gate at `CacheReportPanel.tsx:147` and `CacheReportModal.tsx:111`. Round-3 Codex review finding.
|
|
22
|
+
- Dashboard Cache Report modal settings: the anomaly-threshold popover now rejects fractional input (e.g. `1.5`, `15.9`, `1.0`, `15e2`) inline instead of silently truncating via `parseInt(value, 10)` and POSTing a different value than the user typed. The Save handler trims the input and matches against `/^-?\d+$/` before parsing, so any non-integer literal fails the existing `Must be an integer between 1 and 100` guard. `type="number"` does not block fractional typing/pasting on its own, and only the server previously rejected fractional values. Round-3 Codex review finding.
|
|
23
|
+
- Dashboard Cache Report modal: daily-rows "Cache %" cell color now tracks the same ±`CACHE_REPORT_BAND_PP`=5 band the sparkline draws around today's baseline median, replacing the round-2 binding to `anomaly_reasons.includes('cache_drop')`. The previous binding used the configurable `anomaly_threshold_pp` (default 15) for cell color, so any day 6-14pp below baseline rendered green even though it visibly sat outside the sparkline's highlighted band; raising the configured threshold widened the gap further. The Flag column (`flag-warn` / `flag-ok`) stays bound to each row's own `anomaly_triggered`, so the two signals — display-band coloring vs. per-row anomaly classifier — now stay independent and each carries its own meaning. Round-3 Codex review finding.
|
|
24
|
+
|
|
25
|
+
## [1.10.3] - 2026-05-21
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- `blocks` gap rows now render the correct duration. Pre-fix, `_fmt_gap_duration` in `bin/_lib_render.py` computed `round(total_seconds / 3600)` and clamped sub-hour values to 1, so every gap shorter than 30 minutes surfaced as `"1h gap"` (e.g. a 5-minute or 1-minute gap rendered identically to a 50-minute gap). The bug was hidden until #76: the pre-#76 partition floored block boundaries to 10-minute increments, which usually erased the sub-hour seam between adjacent recorded windows; once #76 routed `(bs, rs)` through `_exact_interval(R)`, real ~1-minute seams became visible and the formatter's coarseness surfaced. The fix mirrors the existing `_fmt_duration_hm` shape: sub-hour gaps render as `"Nm gap"` (with `max(N, 1)` to floor at one minute), 60+ minute gaps render as `"Hh Mm gap"` or `"Hh gap"` when minutes are zero. Additionally, gaps shorter than 60 seconds are suppressed at the phase-3 gap-insertion site in `_group_entries_into_blocks` (`bin/_lib_blocks.py`) — a 1-second seam from a `:59:59` end to a `:00:00` start no longer emits a row. Regression: new `tests/test_blocks_gap_rendering.py` (5 renderer cases — 5m / 30m / 60m / 90m / 12h — plus 2 grouper cases for sub-minute suppression and 65s-threshold boundary); `tests/fixtures/blocks/floor-band-trap/golden-terminal.txt` updated to assert `"5m gap"` on its 5-minute seam. ([#79](https://github.com/omrikais/cctally-dev/issues/79))
|
|
29
|
+
|
|
8
30
|
## [1.10.2] - 2026-05-21
|
|
9
31
|
|
|
10
32
|
### Fixed
|