Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,58 @@

## Unreleased

## v0.32.0 — 2026-08-23 — say it once, and only when you know it

**Row 3 was restating row 2 at greater length.** The flash reader
carried the pinned notice's key and never compared against it, so at
session start the same sentence arrived twice, once short and once
long: `- 12✕5h left · 5.8%/win` over `- budget ~12✕5h left · even
5.8%/win · heading ~85%`. The function's own comment already promised
it "never echoes the pin's own sentence" — now it skips record one and
starts at record two, and row 3 shows the highest-ranked notice the pin
is *not* already carrying. A calm start is two rows, the way it was
supposed to be.

**A 7d projection now needs a day of this window's own evidence.**
Minutes after a weekly rollover the learned walk was projecting last
week's Tuesday onto a pool 2% spent — measured: a 2.4h-old window with
2% used came back `red, dry in 95h`, painted `×` across slot 14
onward, and put `heading ~100%` on the budget line. Every input for
that verdict predates the reset, including the trailing-24h blend the
walk opens with, which describes a day lying on the far side of it.
`SEVEN_DAY_YOUNG_SECS` (86400) silences the learned walk, the linear
at-risk pace, and the dry-cell fallback until the window is a day old
— one gate, so the forecast notices, the `×` cells, the heading, the
accuracy logger and both subcommands go quiet together. Badges still
report real percentages, and a young window that is genuinely spent
still goes red on its own 85%+: the guard mutes pace, never facts. The
5h window is untouched.

**Pace waits for a fraction of its window, not a fixed 15 minutes.**
One gate served both strips: 900 seconds, which is 5% of a 5h window
and 0.1% of a week. An hour into a fresh week, 2% of the pool over
0.6% of the time rendered `3.0✕` in red. The gate is now length/20 —
5h still waits exactly 15 minutes, 7d waits ~8.4h, the same fraction
`seven_day_elapsed` has always called the noise floor.

**The fold token counts windows, not cells: `...▯(✕12)`.** The old
`...▯5h✕10` counted hollow cells the fold hid, while the budget line
beside it priced `12✕5h left` — two arithmetics on one row, and the
reader was left to arbitrate. The 34-cell grid spans 170h against a
168h period, which is where the two differed. The token is now
parenthesized, drops the redundant `5h` unit, and counts the 5h
windows remaining to the true reset: the same number the budget line
prices, from the same instant. Dry tails still read `...×(✕12)` — `×`
the cell, `✕` the operator, one column apart and never the same mark.

**The budget voice drops its sigil.** `!` and `+` mark a notice that
interrupts — you are about to hit something, or there is capacity to
take. The week's resting reading interrupts nothing, and a leading `-`
on a dim line reads as a bullet, which made rows 2 and 3 look like a
two-item list. The pin now reads `12✕5h left · 5.8%/win`. Dim is the
mark; the sentence carries itself. `--check` and `--week` still label
their long form with the word `budget`.

## v0.31.0 — 2026-08-20 — the operator is not a reading

**The ledger row was printing two different X's and calling them both
Expand Down
14 changes: 8 additions & 6 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,27 @@ own signal.
## The ledgers (row 2)

```
5h ▃▄▮▯▯ 0.6x @04:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▮▯▯...▯5hx14 0.7x @Wed 09:00
5h ▃▄▮▯▯ 0.6x @04:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▮▯▯...▯(x14) 0.7x @Wed 09:00
```

One grammar, two scales. `5h` = this window as 5 hours; `7d` = the
period as 34 five-hour slots, oldest left, a gap at each local midnight
*in history only* — the run from `▮` on is contiguous. History draws in
full; the live row folds the 7d future after two kept cells into
`...▯5hx14` — the slots left to the reset, all alike, `×` red when the
tail projects dry (the `week` report still draws every slot). Each strip ends
with its pace (used ÷ elapsed; dim <1x, pressure ≥1x, hidden under 15 min)
and the reset its right edge is — axis labels, not restated badges.
`...▯(x14)` — the 5h windows left before the reset, all alike, `×` red
when the tail projects dry (the `week` report still draws every slot). The
count is the budget line's own, priced from the same instant. Each strip ends
with its pace (used ÷ elapsed; dim <1x, pressure ≥1x, hidden until 5% of the
window has run) and the reset its right edge is — axis labels, not restated
badges.

```
▁▂▃▄▅▆▇█ burned; height = points that cell cost (▁ ≤2 … ▅ ≤11 … █ >20)
ˍ ran, negligible — a bar of height zero, on the baseline
░ unknown — no sample; never drawn as idle
▮ now
▯ ahead — the hollow of ▮
...▯5hx14 the folded future: 14 more 5h slots to the reset (live 7d row)
...▯(x14) the folded future: 14 more 5h windows before the reset (live 7d row)
× pace won't cover it (7d: learned forecast, linear when cold; 5h: linear)
```

Expand Down
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Every component earns its place:
| User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. |
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — `5h[42%@14:30]` reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge is hybrid: day-relative `@5d` while the reset is >= 24h out — decays one day per day, mild and narrow — switching to the same wall-clock `@04:00` inside the last day, where an `@6h`/`@<1h` countdown decayed by the hour exactly when pressure keeps the suffix visible.) When a window's utilization climbs between renders, a reverse-video `+N` token appears right after the badge for ~60s: `5h[44%@14:32]+2` means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. **7d is forecast, not leveled**: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows when relief arrives: `7d[44%@5d]` red means "at your pace, dry days before the reset 5 days from now"; inside the last day it reads `7d[92%@04:00]` — resets at 04:00. Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. **Model-scoped weekly quota**: when the usage API carries a per-model weekly limit (`limits[]`, `kind=weekly_scoped`) for the model your session is running, it renders right after the model+context block — `fabl5[1m][12%] fb[67%]` on a Fable 5 session, `op[33%]` on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining *this* session is signal. Supersedes the legacy `seven_day_opus`/`seven_day_sonnet` fields, which the API now sends as null. |
| Extra usage | Monthly spend, limit, prepaid balance. `--extra auto` shows when quota runs out. |
| Week row | **A row of its own, under the badges**: `5h ▅█▃▮▯ 0.9✕ @23:00 7d ▅▁▂ ▃▅ˍ▃▅ …▮▯▯...▯5h✕19 0.7✕ @Wed 09:00` — this sitting by the hour, the week as its 5h windows (day-gapped, the far future folded to a counted `...▯5h✕19`), height = what each cell burned, `▮` now, `×` where the pool runs dry, each strip ending with its pace and reset. Reconstructed from your own usage log; `auto` shows it once there is history to show. See [Week row](#week-row). |
| Week row | **A row of its own, under the badges**: `5h ▅█▃▮▯ 0.9✕ @23:00 7d ▅▁▂ ▃▅ˍ▃▅ …▮▯▯...▯(✕19) 0.7✕ @Wed 09:00` — this sitting by the hour, the week as its 5h windows (day-gapped, the far future folded to a counted `...▯(✕19)`), height = what each cell burned, `▮` now, `×` where the pool runs dry, each strip ending with its pace and reset. Reconstructed from your own usage log; `auto` shows it once there is history to show. See [Week row](#week-row). |
| Deadman | **Invisible until a switch is armed.** Surfaces [deadman](https://github.com/thevibeworks/deadman) — a dead man's switch that hands the session off when you stop responding. `[☠ armed 42m]` (dim) counts down to the auto-handoff; `[☠ warned 3m]` (yellow) means the phone warning went out; `[☠ due]` means the handoff fires imminently. Sits on the left lane next to the path — it describes this session's lifecycle, not a quota. One `command -v` when the tool is absent, one fast file read when present; nothing armed renders nothing. `--deadman off` disables it. |
| Cache health | **Quiet until it bites.** Claude Code never re-renders an idle session, and while you work the prompt cache is always freshly ~1 TTL from expiry — so a proactive "expiring soon" isn't honestly observable, and `auto` spends no width on it. It speaks only when a rewrite actually happens: `≡!419k` the instant you resume onto a dead cache (idle longer than the TTL) or a mid-session prefix collapse — a 419k-token re-cache at ~20x the read rate (and the same burn on your 5h/7d quota on subscriptions). **Bold red past 200k** — the premium-band miss. `≡~` while a large prefix rebuilds. `--cache always` additionally keeps the freeze-safe deadline `≡@15:20` (last request + TTL; a past time in a frozen frame reads "expired at 15:20"). TTL defaults to 1h (claude.ai subscriber sessions) or 5m (API-key auth); an observed usage breakdown overrides it. The `≡` glyph (U+2261) reads as stacked cache layers — one terminal column, quiet and distinct. |

Expand Down Expand Up @@ -255,7 +255,7 @@ right edge shared with line 1:

```text
proj (main*) +84/-14 8m $6.72 fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%]
- 3✕5h left · 20%/win 5h ▅█▃▮▯ 0.9✕ 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7✕ @Wed 09:00
3✕5h left · 20%/win 5h ▅█▃▮▯ 0.9✕ 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7✕ @Wed 09:00
```

The 5h strip prints no reset: line 1's `5h[38%@23:00]` already carries
Expand All @@ -273,12 +273,12 @@ edge and the full sentence sits flush-left beneath them:
```text
proj (main*) fabl5[1m][██░░42%] fb[66%] [MAX|@work] 5h[38%@23:00] 7d[39%]
5h ▅█▃▮▯ 0.9✕ @23:00 7d ▅▁▂ ▃▅ˍ▃▅ ▃▃▁▂▁ ▅ˍ▂▁▁ ˍˍˍ▃▅ ˍˍˍ▅ ▆▆ˍ▂▮▯▯ 0.7✕ @Wed 09:00
- budget ~3✕5h left · even 20%/win · heading ~52%
budget ~3✕5h left · even 20%/win · heading ~52%
```

Each strip ends with its **pace** (used ÷ elapsed: `0.7✕` is on track,
`1.6✕` caps early — dim below 1✕, pressure-tinted from 1✕, hidden for
the first 15 min of a window) and the **reset** its right edge stands
`1.6✕` caps early — dim below 1✕, pressure-tinted from 1✕, hidden until
5% of the window has run: 15 min for 5h, ~8h for the week) and the **reset** its right edge stands
for (`@23:00` inside 24h, `@Wed 09:00` beyond) — axis labels for a
timeline, not badges restated. When the row shows, the advisor's calm
budget line shows with it (windows left, what even looks like, where
Expand All @@ -292,9 +292,11 @@ fire.
left, the last a 3h stub), height = the 7d points that window burned,
with a thin gap at each local midnight so days read as clusters — and a
day that held five windows shows it — without a ruler. History draws in
full; the future folds: two hollow cells after `▮`, then `...▯5h✕28` —
28 more 5h slots to the reset, all alike (`×` red when the tail projects
dry). The `week` subcommand's wide ledger still draws every slot.
full; the future folds: two hollow cells after `▮`, then `...▯(✕28)` —
28 more 5h windows before the reset, all alike (`×` red when the tail
projects dry). That count is the budget line's own `~28✕5h left`, priced
from the same instant: one row, one arithmetic. The `week` subcommand's
wide ledger still draws every slot.

| Cell | Meaning |
|------|---------|
Expand All @@ -304,8 +306,8 @@ fire.
| `▮` | the cell you are in now |
| `▯` | a cell still ahead of you — the hollow of `▮`, an empty slot waiting |
| `×` | a cell the pool will not cover at the current pace (7d: the learned forecast's dry point, linear when untrained; 5h: linear, the same projection as the badge) |
| `...▯5h✕28` | the folded 7d future: 28 more 5h slots to the reset, one token instead of 28 hollow cells (`×` red when the tail ends dry) |
| `✕` | not a cell — the multiplication sign, the row's one operator (`...▯5h✕28`, `0.7✕`, `- 19✕5h left`). Deliberately not `×` (U+00D7), which is already a reading: cells are the ink, the operator is punctuation, and `...×5h✕28` has to say both at once. One terminal column and no emoji fallback, so the row still meets line 1's edge; override with `MULT_GLYPH` (`╳` and `✖` look stronger but are ambiguous-width and emoji-presentation respectively) |
| `...▯(✕28)` | the folded 7d future: 28 more 5h windows before the reset, one token instead of a run of hollow cells (`×` red when the tail ends dry). The count is windows-to-reset — what the budget line prices — not the number of cells the fold happened to hide |
| `✕` | not a cell — the multiplication sign, the row's one operator (`...▯(✕28)`, `0.7✕`, `19✕5h left`). Deliberately not `×` (U+00D7), which is already a reading: cells are the ink, the operator is punctuation, and `...×(×28)` has to say both at once. One terminal column and no emoji fallback, so the row still meets line 1's edge; override with `MULT_GLYPH` (`╳` and `✖` look stronger but are ambiguous-width and emoji-presentation respectively) |

Burn cells take their badge's pressure color; everything else is neutral,
so the row never adds an alarm channel of its own. Both strips are
Expand Down Expand Up @@ -365,7 +367,7 @@ clauses about one window can never disagree.
| `+ 5h ~40m left · 70% unused` | Said only when the *week* is stranding capacity — an unspent 5h window is otherwise headroom, not waste, since the 5h window is a rate limit and not a budget. |
| `+ ~62% will expire · go heavier` | On pace to strand a large chunk of the subscription. Speaks only to an engaged, unsqueezed session. |
| `+ work 5h[8%] free` | A sibling account in the same shared home is idle while this one is pinned. |
| `- 19✕5h left · 1.1%/win` | The calm budget: runway, what even looks like, where you land (long form). |
| `19✕5h left · 1.1%/win` | The calm budget: runway, what even looks like, where you land (long form). It wears no sigil — `!` and `+` interrupt, the week's resting reading does not. |

`--notice off` keeps row 3 quiet; `--advisor off` silences both. `--check`
and `--week` print the long form, since a terminal command has a whole
Expand Down Expand Up @@ -509,7 +511,7 @@ run. Setting only `CLAUDE_CACHE_DIR` keeps the legacy single-dir behavior.
npm exec --yes bats -- t/
```

418 tests across `t/statusline.bats` (406 statusline + integration) and
423 tests across `t/statusline.bats` (411 statusline + integration) and
`t/install.bats` (12 installer). CI runs on push and PR to `main`.

## Project Structure
Expand Down
Loading
Loading