history_heatmap_dashboard
History Heatmap Dashboard
The Vibe
You open pour and the dashboard already tells you about now — last pour, today count, streak. But it can't show you shape. Did you pour more in March or February? Was last Wednesday a dead zone or a flood? You can feel the rhythm of your captures, but you can't see it.
A GitHub contribution graph solves this exact problem — it turns a year of discrete events into a single glanceable shape. Dense green = active. Gray = quiet. The shape tells a story without reading a single number.
Pour should have this. Not because it's pretty (it is), but because the heatmap is the behavioral feedback loop the manifesto talks about — seeing your capture rhythm makes you want to sustain it.
Screen: pour history or Ctrl+H from the Dashboard
A new TUI screen, not a replacement for the dashboard. The dashboard stays fast and ambient. The heatmap is the deep view you pull up when you want to reflect.
Layout
History Apr 2026
─────────────────────────────────────────────────────────
W1 W2 W3 W4 W5 ... W48 W49 W50
Mon ░░ ██ ░░ ▓▓ ██ ░░ ██ ▓▓
Tue ██ ██ ░░ ░░ ▓▓ ██ ░░ ██
Wed ▓▓ ░░ ██ ██ ░░ ▓▓ ██ ░░
Thu ░░ ▓▓ ▓▓ ██ ██ ░░ ▓▓ ██
Fri ██ ██ ░░ ▓▓ ░░ ██ ██ ░░
Sat ░░ ░░ ░░ ░░ ░░ ░░ ░░ ░░
Sun ░░ ░░ ░░ ░░ ░░ ░░ ░░ ░░
░ 0 ▒ 1-2 ▓ 3-5 █ 6+ Total: 1,247
─────────────────────────────────────────────────────────
Selected: Wed Mar 11 — 4 pours (coffee x3, me x1)
─────────────────────────────────────────────────────────
← → navigate weeks ↑ ↓ navigate days q back
T trim before cursor Esc cancel
Grid Rendering
- Rows: Mon–Sun (7 rows).
- Columns: Weeks. Default viewport shows ~50 weeks (fits in 80-col terminal at 1 char per cell + spacing). Scroll horizontally for older history.
- Cells: Single Unicode block character. Intensity mapped to daily capture count:
░(light shade) — 0 captures▒(medium shade) — 1–2▓(dark shade) — 3–5█(full block) — 6+
- Color: Monochrome by default. Optional per-module color tinting in a future pass.
- Right-aligned: Most recent week at the right edge (like GitHub). Scroll left for older history.
Cell Thresholds
The 4-tier thresholds (0, 1–2, 3–5, 6+) should be adaptive based on the user's actual capture volume. Compute the 25th/50th/75th percentile of non-zero daily counts from the loaded history, use those as the tier boundaries. Fall back to the hardcoded defaults above if the history has fewer than 14 non-zero days (not enough data for meaningful percentiles).
Navigation
| Key | Action |
|---|---|
Left / Right |
Move cursor one week |
Up / Down |
Move cursor one day (Mon-Sun) |
Home |
Jump to oldest entry |
End |
Jump to today |
q / Esc |
Return to dashboard |
Detail Pane
Below the grid, a 1–2 line detail pane shows the selected day:
Wed Mar 11 — 4 pours (coffee x3, me x1)
If the cursor is on a day with 0 captures: Wed Mar 11 — no captures.
Data Source
Reads from the in-memory Vec<HistoryEntry> (already loaded at startup from history.jsonl). No additional I/O. The heatmap is a pure view over data that already exists.
For the grid, precompute a HashMap<NaiveDate, usize> (date -> count) and a HashMap<NaiveDate, HashMap<String, usize>> (date -> module -> count) once on screen entry.
Feature: Atomic History Trim
The Problem
History is append-only and preserved forever by default. But "forever" is a policy, not a constraint. Users should be able to reclaim space or discard old data they no longer care about — but it must be intentional, explicit, and safe.
UX: Trim Before Cursor
From the heatmap screen, the user navigates to a date and presses T. This initiates a trim of all entries strictly before that date.
Confirmation flow (this is destructive — no undo):
┌─────────────────────────────────────────────┐
│ Trim history before Wed Mar 11, 2026? │
│ │
│ This will permanently delete 847 entries │
│ spanning 2025-01-15 to 2026-03-10. │
│ │
│ This cannot be undone. │
│ │
│ Type "trim" to confirm: ░░░░░░ │
│ │
│ Esc to cancel │
└─────────────────────────────────────────────┘
Typing the word "trim" (not just pressing Enter) is the safety gate. This matches the destructive-action UX pattern of tools like gh repo delete.
Implementation: Atomic Trim
The trim operation must be atomic — no partial state on crash or error.
Steps:
- Filter: Partition
Vec<HistoryEntry>intokeep(>= trim date) anddiscard(< trim date). - Write new file: Write
keepentries tohistory.jsonl.tmpas JSONL. - Sync:
file.sync_all()— ensure data is on disk. - Replace:
atomic_replace("history.jsonl.tmp", "history.jsonl")— same utility used by summary cache writes. - Recompute summary:
compute_summary(&keep)and writehistory-summary.json. - Update in-memory state: Replace
self.entrieswithkeep, updateself.summary.
If any step fails, the original history.jsonl is untouched. The .tmp file is the only artifact and can be cleaned up on next load.
Why not append-only for trims? A trim is inherently a rewrite — you're removing data from the middle/beginning of the log. The atomic tmp+rename pattern is the right tool here. Trims are rare (user-initiated, confirmed), so the one-time cost of a full rewrite is fine.
CLI Alternative
pour trim --before 2026-03-11 with the same confirmation prompt (type "trim"). This gives scriptable access without the TUI.
History Method
impl History {
/// Remove all entries before `cutoff_date` (exclusive).
/// Atomic: writes a new JSONL file, syncs, then replaces the original.
pub fn trim_before(&mut self, cutoff: NaiveDate) -> Result<TrimResult>;
}
pub struct TrimResult {
pub removed: usize,
pub remaining: usize,
}
What This Doesn't Include (Yet)
- Per-module heatmap filtering — e.g., show only coffee captures. Natural extension, but adds complexity to the grid rendering. Defer.
- Export from heatmap — selecting a date range and exporting to CSV/JSON. The JSONL file is already the export format;
jqand friends handle this. - Undo trim — intentionally absent. The confirmation flow is the safety net. If users want backup, they can
cp history.jsonl history.jsonl.bakbefore trimming. Pour is not a backup tool. - Color themes / module tinting — the monochrome shade approach works in any terminal. Color is a nice-to-have for later.
Open Questions
- Keybinding:
Ctrl+Hfrom the dashboard, or a dedicatedpour historysubcommand, or both? Leaning both —Ctrl+Hfor when you're already in the TUI, subcommand for direct access. - Adaptive thresholds: The percentile-based approach means two users see different intensity for the same count. Is this desirable (personalized) or confusing (inconsistent)?
- Month labels: GitHub shows month labels above the grid. Worth the vertical space, or just show the current cursor's month in the header?