Skip to content

feat: add --days flag with hourly heatmap for short periods - #19

Open
kyleboddy wants to merge 2 commits into
JeanMeijer:mainfrom
kyleboddy:feat/days-flag-hourly-grid
Open

feat: add --days flag with hourly heatmap for short periods#19
kyleboddy wants to merge 2 commits into
JeanMeijer:mainfrom
kyleboddy:feat/days-flag-hourly-grid

Conversation

@kyleboddy

@kyleboddy kyleboddy commented Mar 15, 2026

Copy link
Copy Markdown

Summary

  • Adds a --days / -d flag to control the lookback window (default remains 365 days / rolling year)
  • For periods ≤ 21 days, automatically switches to hourly granularity — the grid becomes 24 hour-columns × N day-rows so short windows fill the image with real usage patterns instead of a few tiny squares
  • Yearly / daily mode is completely unchanged

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

File Change
cli.ts --days/-d option, auto-selects hour vs day granularity
graph.ts getHourlyGrid() builder (24h cols × N-day rows), parameterized getSectionLayout for 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 counts
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 Threads granularity option through, sets dateKeyFn before loading
claude-code.ts Uses getDateKeyFn() for history display value keys
interfaces.ts Granularity type (`"day"

Usage

# Short periods — automatically hourly grid
slopmeter --days 7
slopmeter -d 14 --dark

# Default — unchanged yearly behavior
slopmeter

# Longer custom periods — daily grid
slopmeter --days 90

How to build & test

bun install
bun run build
node packages/cli/dist/cli.js --days 7 --claude -o heatmap-7d.png
node packages/cli/dist/cli.js --claude -o heatmap-yearly.png

Test plan

  • slopmeter --days 2 — 3-row hourly grid, dates labeled
  • slopmeter --days 7 — 8-row hourly grid, full week with hour-of-day patterns visible
  • slopmeter --days 14 — 15-row hourly grid, two-week range
  • slopmeter (no flag) — unchanged yearly rolling heatmap
  • slopmeter --days 30 — falls back to daily grid (> 21 day threshold)
  • Streaks, model insights, token totals all correct in both modes
  • --all merge mode works with hourly granularity
  • --format json export works with hourly data

🤖 Generated with Claude Code


Summary by cubic

Adds a --days/-d flag 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 N to set lookback (default 365); strictly validates positive integers (rejects non-integers).
    • Auto granularity: ≤21 days → hourly; otherwise daily.
    • Hourly grid: 24 hour-columns, day-rows, 3‑hour column labels, and weekday + date row labels.
    • Works for PNG/SVG/JSON exports, --all merge, insights, and streaks; JSON emits hourly timestamps in hourly mode.
  • Refactors

    • Introduced Granularity type and pluggable dateKeyFn for day vs hour.
    • Parameterized grid layout for variable row counts/cell sizes; fixed note overlay position.
    • Providers set dateKeyFn before loading; history keys now use getDateKeyFn().
    • Added parseTimeKey and day-level de-dup to compute streaks correctly with hourly data.

Written for commit 38578b0. Summary will update on new commits.

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/cli/src/cli.ts
Comment thread packages/cli/src/cli.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-ai with guidance or docs links (including llms.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,

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

Comment thread packages/cli/src/cli.ts Outdated
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant