07 — Run-out projection semantics
Documents projection vocabulary, available quota fields, and why jackin❯ usage surfaces remain limited to provider limits and resets.
Summary
Provider APIs do not supply run-out forecasts, and client-derived depletion estimates are outside the jackin❯ limits-only usage contract.
Question and scope
(1) What pace/reset math does jackin-usage already compute? (2) Which run-out phrases and formulas appear in competitor or compatibility evidence? (3) Which API fields would such derivations require, and where are they absent? (4) Why do these findings not expand the jackin❯ limits-only contract?
This chapter records competitor and API evidence. It does not recommend a projection surface for jackin❯.
Method
Method: codebase read + web (READMEs only) Verification cutoff: 2026-07-24
Findings
Q1 — What jackin-usage already computes
quota_pace_label(remaining_percent: Option<u8>, reset_at: Option<i64>, window_seconds: Option<i64>, now: i64) -> Option<String>is the sole pace producer —crates/jackin-usage/src/usage/format.rs:164-191(confidence: HIGH)- Exact math:
reset_in = max(reset_at − now, 0)(format.rs:171);window_seconds = max(window_seconds, 1)(format.rs:172); ifreset_in > window_seconds→None, pace suppressed entirely (format.rs:173-175);time_left_percent = reset_in / window_seconds × 100(format.rs:176);delta = remaining_percent − time_left_percent(format.rs:183) (confidence: HIGH) - Label mapping:
|delta| ≤ 2.0→"On pace";delta > 0→"{round(delta)}% in reserve";delta < 0→"{round(−delta)}% in deficit"— so deficit N = round(time_left_percent − remaining_percent) when positive, reserve is the mirror — format.rs:184-190 (confidence: HIGH) - In-code comment attributes this to the "CodexBar pace model": more quota than time = reserve, burning faster than the clock = deficit — format.rs:177-182 (confidence: HIGH that the comment says so; attribution itself unverified, see Q3)
- Unit tests pin the formula: 60% left with 90% of window remaining →
"30% in deficit"; 90% left with 60% remaining →"30% in reserve"; 50/50 →"On pace"—crates/jackin-usage/src/usage/tests.rs:2821-2835(testquota_pace_label_uses_codexbar_reserve_deficit_onpace) (confidence: HIGH) - Integration coverage: Codex Session bucket
"15% in reserve"(tests.rs:2255); Codex Weekly bucket falls back to the window label"1 week window"when pace isNone(tests.rs:2259; fallback atcrates/jackin-usage/src/usage/codex.rs:696-698); Kimi Rate Limit"30% in reserve", Kimi Weekly paceNone(tests.rs:3318, 3324) (confidence: HIGH) reset_label(reset_at, now):"Resets now"whenreset_at ≤ now; default Countdown style"Resets in {compact_duration} ({local clock})"; ExactClock alternative"Resets {local clock}"— format.rs:111-128. Duration formatting"{d}d {h}h"/"{h}h {m}m"/"{m}m"viacompact_duration_label— format.rs:193-208 (confidence: HIGH). So"Resets in <duration>"is already shipped.remaining_from_fraction:remaining = 100 − min(used, 100)asu8, where used resolves a fraction-vs-percent ambiguity (value ≤ 1.0→ ×100) and non-finite/negative inputs (provider "unknown" sentinels) yieldNone— format.rs:39-47, 62-74 (confidence: HIGH). Remaining is quantized to whole percent (u8).window_secondsis supplied per provider at probe time, not by the protocol: Claude constantsCLAUDE_SESSION_WINDOW_SECONDS = 5h,CLAUDE_WEEKLY_WINDOW_SECONDS = 7d(crates/jackin-usage/src/usage/claude.rs:447, 450), mapped by limitkind(claude.rs:555-576), pace computed ininto_bucket(claude.rs:505-506); Codex uses APIlimit_window_secondsorwindow_duration_mins × 60(crates/jackin-usage/src/usage/codex.rs:389-392); Kimi supplies window seconds only for the"Rate Limit"bucket (crates/jackin-usage/src/usage/kimi.rs:252-256) (confidence: HIGH)- The capsule TUI already renders "Lasts until reset" today, derived from the pace token, not from a burn-rate projection: in
usage_stacked_bucket_detail_rows, a detail equal to"On pace"or ending" in reserve"sets a flag (crates/jackin-capsule/src/tui/components/dialog_widgets/usage.rs:754-755); if the flag is set, a"Resets…"detail exists, and no"Runs out…"detail is present,"Lasts until reset"is pushed into the right column (usage.rs:760-764) (confidence: HIGH). Note:"On pace"covers delta down to −2, so a bucket slightly behind pace still shows "Lasts until reset". "Runs out in <duration>"is consumed but never produced: the dialog routes any detail starting"Runs out"to the right column (dialog_widgets/usage.rs:751) and suppresses the synthesized "Lasts until reset" when one is present (usage.rs:762), but no code in the repo generates the string. It appears only in test fixtures as an injected compositepace_label"31% in deficit · Runs out in 21h 45m"—crates/jackin-capsule/src/tui/components/dialog/tests.rs:1587, 1643, asserted rendered at tests.rs:1624-1625 — showingpace_labelwith" · "separators is the intended carrier (splitterusage_quota_bucket_detail_parts, dialog_widgets/usage.rs:846) (confidence: HIGH)"Projected empty"appears nowhere in the repository (repo-wide grep, zero hits) (confidence: HIGH)pace_labelis persisted as an opaque string column in the snapshot store —crates/jackin-usage/src/usage_snapshot_store.rs:213(schema), :450 (bucket→row) (confidence: HIGH)
Q2 — Projection formulas found in the evidence (analysis only)
- Input availability in the bucket model:
QuotaBucketViewcarriesremaining_percent: Option<u8>(crates/jackin-protocol/src/control.rs:600-601),resets_at: Option<i64>(control.rs:604-608), and preformattedreset_label/pace_labelstrings (control.rs:602-603, 612-613) — but nowindow_secondsfield. Window duration exists only provider-side at probe time (Q1) and is not in the protocol bucket nor in the store schema (column list, usage_snapshot_store.rs:190-218). Any projection computed downstream of bucket construction lacks the window duration unless the contract changes; computed at probe time (wherequota_pace_labelruns today) all three inputs are present (confidence: HIGH) - Sampling timestamps exist:
FocusedUsageView.fetched_at_epoch(control.rs:407); store columnfetched_at(usage_snapshot_store.rs:203) (confidence: HIGH)
(a) "Runs out in" / "Projected empty in" — burn-rate variants derivable from the available inputs:
- Variant A — linear-from-window-start (no history needed). Inputs: the exact trio
quota_pace_labelalready takes (format.rs:164-169). Defineelapsed = window_seconds − reset_in,used = 100 − remaining. Burn rater = used / elapsed(%/s);runs_out_in = remaining / r = remaining × elapsed / used. Failure modes:used = 0→ division by zero (never runs out);elapsed = 0at window start → undefined; early-window jumpiness — smallelapsedamplifies the whole-percentu8quantization ofremaining(format.rs:45-47, control.rs:601); assumes the window started atresets_at − window_seconds, which is wrong for rolling windows;reset_in > window_secondsalready suppressed by the existing guard (format.rs:173-175) (confidence: HIGH that inputs/derivation follow from cited code; the variant itself is a derivation, not shipped code) - Algebraic identity linking Variant A to the shipped pace math:
runs_out_in ≥ reset_in⇔remaining × (window − reset_in) ≥ (100 − remaining) × reset_in⇔remaining × window ≥ 100 × reset_in⇔remaining_percent ≥ time_left_percent⇔delta ≥ 0. So under Variant A, the "Lasts until reset" boolean is exactly the sign of the existing pace delta (format.rs:176-183) — the current TUI heuristic (Q1, dialog_widgets/usage.rs:754-764) is that model's boolean, off by the ±2-point "On pace" band (confidence: HIGH, pure algebra over cited formulas) - Variant B — nominal-pace projection (degenerate; no burn estimate). Assume plan-nominal burn
r = 100 / window_seconds;runs_out_in = remaining × window_seconds / 100. Inputs:remaining+window_secondsonly (noresets_atneeded for the duration). Yields the same "lasts until reset" boolean as Variant A (same inequality), but the duration reflects nominal, not actual, consumption — it contradicts a "current burn rate" reading of the phrase. Failure modes: none numerically (no division by observed data), but semantically misleading for idle or bursty accounts (confidence: HIGH as derivation) - Variant C — sampled burn from consecutive snapshots.
r = (remaining_prev − remaining_now) / (fetched_now − fetched_prev);runs_out_in = remaining_now / r. Data requirement: ≥ 2 timestamped snapshots per bucket. Availability: the refresh loop holds exactly one prior in-memory view per cache key and reads it before overwriting (self.snapshots: HashMap<String, CachedUsage>—crates/jackin-usage/src/usage.rs:192; read-then-insert at usage.rs:521-526), so one delta sample is obtainable in-memory; the durable store retains no history (Q4). Failure modes: reset crossing between samples →remainingrises → negativer; slow burn + 1% quantization + refresh cadence → measured delta 0 →r = 0→ projection flaps between "never" and finite; single-delta estimates are jumpy and smoothing (e.g. EWMA) needs more than one prior sample; division by zero whenfetched_now = fetched_prev(confidence: HIGH for the cited data availability; the variant is a derivation)
(b) "Lasts until reset":
- Definition derivable from any variant: projected run-out instant
now + runs_out_in ≥ resets_at. Under Variants A and B this reduces toremaining_percent ≥ time_left_percent(identity above); under Variant C it is an independent comparison using the sampledr(confidence: HIGH as derivation) - jackin❯ already ships the phrase as a pace-token heuristic (Q1):
"On pace"-or-"in reserve"+ a"Resets"detail + no"Runs out"detail →"Lasts until reset"— dialog_widgets/usage.rs:746-764 (confidence: HIGH)
Q3 — CodexBar / OpenUsage README cross-check (READMEs only, clean-room)
- CodexBar README (https://github.com/steipete/CodexBar) describes usage display only as meters + reset countdowns: "Provider-specific usage meters with reset countdowns."; "Per-provider session, weekly, and monthly windows with countdowns to the next reset"; "The menu bar icon is a tiny usage meter." It contains no statements about pace, projection, burn rate, run-out, deficit, reserve, time-to-empty, or "lasts until" semantics — checked twice (repo page and raw README), consistent results (confidence: MED — read via WebFetch summarizer, quotes consistent across two independent fetches but not byte-verified)
- OpenUsage README (https://github.com/robinebers/openusage) names the feature without defining it: "Provider-grouped meters with live reset countdowns and pace indicators." No definition of the pace computation and no run-out/projection/deficit/reserve/time-to-empty language anywhere in the README — checked twice (repo page and raw README) (confidence: MED — same WebFetch caveat)
- Consequence: the jackin❯ comment "CodexBar pace model" (format.rs:177) and the target phrases "Runs out in" / "Projected empty in" / "Lasts until reset" cannot be corroborated or refuted from the allowed public sources; the reserve/deficit/on-pace semantics are documented only by jackin❯ code and tests (confidence: HIGH that the READMEs are silent, per the MED-confidence fetches above)
Q4 — Snapshot store history retention
- The store retains only the latest row per bucket, not history:
UNIQUE(provider, account_key_hash, source, window_kind)— usage_snapshot_store.rs:217 — withINSERT … ON CONFLICT(provider, account_key_hash, source, window_kind) DO UPDATE SET …overwriting every field includingremaining_percent,resets_at,fetched_at,pace_label— usage_snapshot_store.rs:347-368 (confidence: HIGH) - Test proof: two sequential
store_usage_snapshotcalls on a 2-bucket view still yield exactly 2 rows, with the Session row'sremaining_percentreplaced by the second write's value (25) andfetched_atadvanced —crates/jackin-usage/src/usage_snapshot_store/tests.rs:110-139(testaccount_snapshot_rows_are_persisted_and_upserted) (confidence: HIGH) - Therefore a sampled burn rate (Q2 Variant C) cannot be computed from the durable store as-is; the only prior sample currently available is the single in-memory
CachedUsageview per cache key (usage.rs:192, 521-526), which supports one delta and no smoothing window (confidence: HIGH)
Dead ends and contradictions
- No producer of
"Runs out in"exists anywhere in the repository — repo-wide grep acrosscrates/andsrc/finds only the TUI consumer (dialog_widgets/usage.rs:751, 762) and injected test fixtures (dialog/tests.rs:1587, 1643). Ruled out that the projection is already computed somewhere upstream. - No occurrence of
"Projected empty"or any burn-rate identifier (burn_rate, "time to empty") in product code — the phrase family is entirely unimplemented. window_secondschecked for persistence: absent from both the protocolQuotaBucketView(control.rs:593-628) and the store schema (usage_snapshot_store.rs:190-218) — ruled out as a downstream-available input.- Mild contradiction: the TUI's shipped "Lasts until reset" heuristic fires for
"On pace"(delta as low as −2), i.e. a bucket marginally behind pace is still labeled as lasting until reset — dialog_widgets/usage.rs:754-755 vs. format.rs:184-185. Consistent with rounding tolerance, inconsistent with a strictdelta ≥ 0reading. - CodexBar README fetched twice specifically hunting projection language — none present; the in-code "CodexBar pace model" attribution (format.rs:177) cannot be verified from the README, and source reading is forbidden (clean-room).
- No embedded instructions were encountered in any fetched or read content (code, tests, READMEs) — nothing to flag.
Implications for jackin❯
jackin❯ should not produce or display client-derived run-out projections. Usage surfaces remain limited to provider-authoritative used or remaining percentages, reset windows, plan status, freshness, and provenance. Existing projection consumers should not be treated as a product requirement.
Limitations and unknowns
- CodexBar displays "Runs out in 4d 21h" and "Lasts until reset" on its homepage; release notes confirm the bare "Projected empty" label. OpenUsage uses "~N% left at reset" instead. The backing math remains unpublished — chapter 10.
- Claude weekly and Grok weekly use fixed slots; Claude's five-hour session is anchored on server
resets_at; Codex exposes a discrete window withwindow_start = reset_at − limit_window_seconds, though usage may not start at zero. Variant A is therefore well-founded when anchored onresets_at— chapters 09 and 10. - The intended producer of the composite fixture
"31% in deficit · Runs out in 21h 45m"(dialog/tests.rs:1587) — the TUI contract anticipates a producer that does not exist; whether it was planned forjackin-usageprobe-time or a Desktop-side computation is unrecorded. - README quotes carry a WebFetch-summarizer intermediary; consistent across two fetches each but not byte-verified against raw file bytes.
Sources
crates/jackin-usage/src/usage/format.rs— current pace and reset formulas.crates/jackin-usage/src/usage_snapshot_store.rs— snapshot retention contract.