feat: add --days flag with hourly heatmap for short periods - #19
feat: add --days flag with hourly heatmap for short periods#19kyleboddy wants to merge 2 commits into
Conversation
Add a --days/-d flag to control the lookback period instead of the hardcoded rolling year. For periods of 21 days or fewer, the heatmap automatically switches to hourly granularity (24 hour-columns × N day-rows) so the grid fills the image with meaningful data instead of showing a handful of tiny squares. Changes: - cli.ts: new --days/-d option, auto-selects granularity (hour vs day) - graph.ts: hourly grid builder (getHourlyGrid), parameterized section layout for variable row count/cell size, dynamic row labels with weekday + date, 3-hour column labels, fixed note overlay position - utils.ts: formatLocalHour key function, module-level dateKeyFn for aggregation granularity, parseTimeKey for bidirectional key parsing, deduplicateByDay for correct streak computation with hourly data - providers.ts: thread granularity to set dateKeyFn before loading - claude-code.ts: use getDateKeyFn() for history display values - interfaces.ts: Granularity type Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b37c22823c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
2 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/cli.ts">
<violation number="1" location="packages/cli/src/cli.ts:305">
P2: `--days` uses `parseInt`, so malformed numeric strings are accepted and truncated instead of being rejected as invalid positive integers.</violation>
</file>
<file name="packages/cli/src/lib/claude-code.ts">
<violation number="1" location="packages/cli/src/lib/claude-code.ts:385">
P1: Hourly granularity is inconsistent with stats-cache dedupe: day-level `row.date` is checked against hour-level `coveredDates`, causing duplicate counting and midnight mis-bucketing.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -12,6 +12,7 @@ import { | |||
| addModelTokenTotals, | |||
There was a problem hiding this comment.
P1: Hourly granularity is inconsistent with stats-cache dedupe: day-level row.date is checked against hour-level coveredDates, causing duplicate counting and midnight mis-bucketing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/lib/claude-code.ts, line 385:
<comment>Hourly granularity is inconsistent with stats-cache dedupe: day-level `row.date` is checked against hour-level `coveredDates`, causing duplicate counting and midnight mis-bucketing.</comment>
<file context>
@@ -381,7 +382,7 @@ async function loadClaudeHistoryDisplayValues(
}
- const dateKey = formatLocalDate(timestamp);
+ const dateKey = getDateKeyFn()(timestamp);
if (coveredDates.has(dateKey)) {
</file context>
- Reject non-integer --days values like "7d" or "1.5" by using Number() + Number.isInteger() instead of parseInt() which silently truncates - Pass granularity to toJsonUsageSummary so --format json with --days ≤ 21 emits hourly timestamps (e.g. "2026-03-12T14") instead of collapsing all 24 hourly buckets to the same date string Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
--days/-dflag to control the lookback window (default remains 365 days / rolling year)Motivation
For new installs or short lookback periods, the existing yearly grid produces either a mostly-empty image (yearly default) or a handful of cells in a tiny image (
--days 7→ 2 columns × 7 rows). The hourly grid fills the same canvas width with ~168 cells for 7 days, showing when-in-the-day activity occurred.What changed
cli.ts--days/-doption, auto-selectshourvsdaygranularitygraph.tsgetHourlyGrid()builder (24h cols × N-day rows), parameterizedgetSectionLayoutfor variable row count / cell size, dynamic row labels (Mon 3/12), 3-hour column labels (12a 3a 6a …), fixed note overlay position for variable row countsutils.tsformatLocalHourkey function, module-leveldateKeyFnfor aggregation granularity,parseTimeKeyfor bidirectional key parsing,deduplicateByDayfor correct streak computation with hourly dataproviders.tsgranularityoption through, setsdateKeyFnbefore loadingclaude-code.tsgetDateKeyFn()for history display value keysinterfaces.tsGranularitytype (`"day"Usage
How to build & test
Test plan
slopmeter --days 2— 3-row hourly grid, dates labeledslopmeter --days 7— 8-row hourly grid, full week with hour-of-day patterns visibleslopmeter --days 14— 15-row hourly grid, two-week rangeslopmeter(no flag) — unchanged yearly rolling heatmapslopmeter --days 30— falls back to daily grid (> 21 day threshold)--allmerge mode works with hourly granularity--format jsonexport works with hourly data🤖 Generated with Claude Code
Summary by cubic
Adds a
--days/-dflag to set the lookback window. For windows ≤ 21 days, the heatmap switches to an hourly grid (24 hours × N days) so short periods show real daily patterns; yearly/daily mode is unchanged.New Features
-d, --days Nto set lookback (default 365); strictly validates positive integers (rejects non-integers).--allmerge, insights, and streaks; JSON emits hourly timestamps in hourly mode.Refactors
Granularitytype and pluggabledateKeyFnfordayvshour.dateKeyFnbefore loading; history keys now usegetDateKeyFn().parseTimeKeyand day-level de-dup to compute streaks correctly with hourly data.Written for commit 38578b0. Summary will update on new commits.