cctally 1.20.2 → 1.20.3

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,11 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.20.3] - 2026-05-28
9
+
10
+ ### Fixed
11
+ - **The dashboard's Blocks panel no longer renders the just-started active block with a `~` heuristic marker, and the previous block no longer silently disappears from the panel ~20 minutes after a 5h reset.** Both symptoms came from the same root cause in `_select_non_overlapping_recorded_windows`: when two genuinely-adjacent canonical 5h windows had `resets_at` values whose 10-minute-floored keys landed less than 5h apart (e.g. OLD `R=09:00:01Z` → floor `09:00`, NEW `R=13:59:59Z` → floor `13:50` — a 4h 50m floored-distance for a real block-pair with a 2-second sub-second overlap on the boundary), the weighted-interval-scheduling DP treated them as conflicting and dropped the lighter-weighted one. At `t≈0` after a reset that meant the NEW canonical window vanished from the selector's output, so the dashboard's Blocks panel partitioned NEW-window entries into the heuristic leftover bucket and rendered them with `anchor='heuristic'` and a `_floor_to_hour` `start_at` (the `~` chip in `BlocksPanel.tsx:33-34`); ~20 minutes later, once the NEW window's snapshot count caught up, the DP would flip and drop OLD instead, making the previous block silently disappear. CLI `cctally blocks --active` was unaffected (it queries `five_hour_blocks` directly with `is_closed=0`, bypassing this scheduler). Fix: any item carrying the canonical-source weight overlay (`_CANONICAL_WEIGHT_THRESHOLD = 1000`) is force-restored to the result set if the DP dropped it — `maybe_update_five_hour_block` already deduplicated via `_canonical_5h_window_key` pre-insert, so two canonical rows are by definition non-overlapping physically and don't need re-arbitration on a floor-distance check. The DP still runs over the full item set (canonical + raw), so a non-canonical phantom R adjacent to a canonical anchor in a different floor bucket continues to lose by weight comparison; the bypass only restores canonical items, never adds back a raw-only phantom. (#116)
12
+
8
13
  ## [1.20.2] - 2026-05-28
9
14
 
10
15
  ### Fixed
package/bin/cctally CHANGED
@@ -3951,11 +3951,21 @@ def _resolve_block_selector(
3951
3951
  return dict(row) if row else None
3952
3952
 
3953
3953
 
3954
+ # Items in `_select_non_overlapping_recorded_windows` whose weight is
3955
+ # >= this threshold are treated as canonical (sourced from
3956
+ # ``five_hour_blocks``) and bypass the 5h non-overlap constraint. Must
3957
+ # stay in sync with the ``counts[snapped] = counts.get(snapped, 0) + 1000``
3958
+ # overlay in ``_load_recorded_five_hour_windows`` — same constant, same
3959
+ # meaning. Issue #116.
3960
+ _CANONICAL_WEIGHT_THRESHOLD = 1000
3961
+
3962
+
3954
3963
  def _select_non_overlapping_recorded_windows(
3955
3964
  items: list[tuple[dt.datetime, int]],
3956
3965
  ) -> list[dt.datetime]:
3957
3966
  """Pick the max-weight subset of recorded ``R`` values that respect
3958
- the 5h non-overlap constraint.
3967
+ the 5h non-overlap constraint, with canonical anchors guaranteed
3968
+ to survive.
3959
3969
 
3960
3970
  Anthropic 5h windows cannot truly overlap: the next window only
3961
3971
  opens once the previous one resets, so consecutive real ``R``
@@ -3968,6 +3978,23 @@ def _select_non_overlapping_recorded_windows(
3968
3978
  snapshots: the subset that maximizes total support wins. Tie-break
3969
3979
  in the take branch favors including more ``R`` values.
3970
3980
 
3981
+ Canonical bypass (issue #116): any item with weight at least
3982
+ ``_CANONICAL_WEIGHT_THRESHOLD`` came from the authoritative
3983
+ ``five_hour_blocks`` rollup (the caller overlays at +1000).
3984
+ ``maybe_update_five_hour_block`` already deduped via
3985
+ ``_canonical_5h_window_key`` pre-insert, so two canonical rows are
3986
+ by definition non-overlapping physically — they only appear "in
3987
+ conflict" here when their 10-min-floored keys land less than
3988
+ ``BLOCK_DURATION`` apart, which happens at every real reset
3989
+ boundary when Anthropic's ``resets_at`` jitters sub-second across
3990
+ the boundary (e.g. OLD ``R=09:00:01Z`` floors to ``09:00``, NEW
3991
+ ``R=13:59:59Z`` floors to ``13:50`` — 4h 50m floored-distance for
3992
+ a genuinely-adjacent block pair). The DP still runs over the full
3993
+ item set so non-canonical phantoms next to a canonical anchor get
3994
+ dropped by weight comparison; the canonical-bypass only force-
3995
+ restores canonical items the DP dropped, never adds back a raw-
3996
+ only phantom.
3997
+
3971
3998
  Args:
3972
3999
  items: ``(R, support_count)`` pairs.
3973
4000
 
@@ -4012,6 +4039,11 @@ def _select_non_overlapping_recorded_windows(
4012
4039
  else:
4013
4040
  i -= 1
4014
4041
  chosen.reverse()
4042
+ # Canonical bypass: force-restore any canonical-weight anchor the DP
4043
+ # dropped (issue #116). Keep the result sorted ascending.
4044
+ canonical = {R for R, w in items_sorted if w >= _CANONICAL_WEIGHT_THRESHOLD}
4045
+ if canonical and not canonical.issubset(chosen):
4046
+ return sorted(set(chosen) | canonical)
4015
4047
  return chosen
4016
4048
 
4017
4049
 
@@ -4297,7 +4329,7 @@ def _load_recorded_five_hour_windows(
4297
4329
  # (only credit-truncated entries land there).
4298
4330
  if snapped in block_start_overrides:
4299
4331
  truncated_anchors.add(snapped)
4300
- counts[snapped] = counts.get(snapped, 0) + 1000
4332
+ counts[snapped] = counts.get(snapped, 0) + _CANONICAL_WEIGHT_THRESHOLD
4301
4333
 
4302
4334
  non_truncated_items = [
4303
4335
  (a, w) for a, w in counts.items() if a not in truncated_anchors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cctally",
3
- "version": "1.20.2",
3
+ "version": "1.20.3",
4
4
  "description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
5
5
  "homepage": "https://github.com/omrikais/cctally",
6
6
  "repository": {