pi-minimal-footer 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.3] - 2026-10-07
11
+
12
+ ### Changed
13
+
14
+ - Add square brackets around the subscription usage bar so its beginning and end remain visible when empty
15
+ - Randomize subscription usage fallback refreshes by ±⅛ of the four-minute interval (3.5–4.5 minutes), preserving reset-time checks and deadlines across unrelated window updates
16
+ - Raise the subscription usage request timeout from five to nine seconds before aborting stalled authentication, fetch, or response-body waits
17
+
10
18
  ## [0.3.2] - 2026-10-06
11
19
 
12
20
  ### Changed
@@ -147,7 +155,8 @@ First npm-ready release.
147
155
 
148
156
  - Project scaffold — `extensions/index.ts` with basic footer structure, `package.json` with pi extension manifest, `README.md`, `LICENSE` (MIT)
149
157
 
150
- [Unreleased]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.3.2...HEAD
158
+ [Unreleased]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.3.3...HEAD
159
+ [0.3.3]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.3.2...v0.3.3
151
160
  [0.3.2]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.3.1...v0.3.2
152
161
  [0.3.1]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.3.0...v0.3.1
153
162
  [0.3.0]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.2.0...v0.3.0
package/README.md CHANGED
@@ -57,7 +57,7 @@ Edit `minFooter` in Pi's agent `settings.json` (normally `~/.pi/agent/settings.j
57
57
 
58
58
  `⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀↻1d8h` shows **subscription allowance used**, not context usage.
59
59
 
60
- - Bar: 5–10 cells, expanding into spare space; green below 85%, amber from 85%, red from 92%. Dim means cached after a refresh failure. Very narrow terminals may show fewer cells or only the reset label.
60
+ - Bar: shown as `[cells]` with 5–10 cells, expanding into spare space; green below 85%, amber from 85%, red from 92%. The brackets keep the bar’s beginning and end visible even when it is empty. Dim means cached after a refresh failure. Very narrow terminals may show fewer cells or only the reset label.
61
61
  - Reset: local `↻HH:mm` through 24 hours; whole days/hours above 24 hours (`↻1d8h`); whole days above 10 days (`↻12d`). Remaining durations round down. No reset reported means bar only.
62
62
  - Shows the shortest available usage window for the selected provider. Missing credentials, unsupported/local models, or absent limits hide the usage bar. Custom proxy endpoints are not polled.
63
63
 
package/VISION.md CHANGED
@@ -55,7 +55,7 @@ Reuse Pi's selected-provider credentials. Copilot uses the GitHub login token fr
55
55
  ### Refresh and failure behavior
56
56
 
57
57
  - Consume passive Codex response headers/stream events and Claude quota headers when available.
58
- - Otherwise fetch on startup/provider switch, then every four minutes without a fresh response update; also refresh at the reported reset time.
58
+ - Otherwise fetch on startup/provider switch, then roughly every four minutes without a fresh response update. Sample independent uniform ±⅛ jitter (3.5–4.5 minutes) for each attempt or fresh displayed-window update to spread fallback requests across sessions. Keep that deadline across unrelated window updates; a reported reset time still triggers an earlier refresh.
59
59
  - Merge partial windows. A weekly-only signal must not replace a cached shorter window or postpone its refresh.
60
60
  - Bound authentication, fetch, and response-body parsing by a shared five-second timeout. Release stalled work so polling can recover.
61
61
  - Provider switches clear usage immediately, cancel pending work, and invalidate late results. Gemini model switches also refresh model-specific usage.
@@ -6,7 +6,8 @@ import { parseUsageWindows, record, shortest, supportedOrigin, usageRequest, win
6
6
  import type { UsageWindow } from "./usage-providers.js";
7
7
 
8
8
  const REFRESH_MS = 4 * 60_000;
9
- const REQUEST_TIMEOUT_MS = 5000;
9
+ const REFRESH_JITTER_MS = REFRESH_MS / 8;
10
+ const REQUEST_TIMEOUT_MS = 9000;
10
11
  const HOUR_MS = 60 * 60_000;
11
12
  const DAY_MS = 24 * HOUR_MS;
12
13
  const CELL_STEPS = 8;
@@ -115,6 +116,7 @@ export class UsageLimits {
115
116
  private updatedAt: Record<string, number> = {};
116
117
  private stale = false;
117
118
  private attemptedAt = 0;
119
+ private refreshDelay = REFRESH_MS;
118
120
  private timer: ReturnType<typeof setTimeout> | null = null;
119
121
  private request: AbortController | null = null;
120
122
 
@@ -178,17 +180,23 @@ export class UsageLimits {
178
180
  // Only a response for the displayed window supersedes its background refresh.
179
181
  this.cancelRequest();
180
182
  this.stale = false;
183
+ this.randomizeRefresh();
181
184
  }
182
185
  this.schedule();
183
186
  this.render();
184
187
  }
185
188
 
189
+ private randomizeRefresh(): void {
190
+ this.refreshDelay = REFRESH_MS + (Math.random() * 2 - 1) * REFRESH_JITTER_MS;
191
+ }
192
+
186
193
  private schedule(): void {
187
194
  if (this.timer) clearTimeout(this.timer);
188
195
  this.timer = null;
189
196
  if (!this.ctx || this.request) return;
190
197
  const selected = this.selectedWindow();
191
- let next = Math.max(this.attemptedAt, selected ? this.updatedAt[selected[0]] : 0) + REFRESH_MS;
198
+ // Keep the sampled deadline when unrelated windows reschedule the timer.
199
+ let next = Math.max(this.attemptedAt, selected ? this.updatedAt[selected[0]] : 0) + this.refreshDelay;
192
200
  const resetAt = selected?.[1].resetAt;
193
201
  if (resetAt != null && resetAt > this.attemptedAt) next = Math.min(next, resetAt);
194
202
  this.timer = setTimeout(() => { this.timer = null; void this.poll(); }, Math.max(1, next - Date.now()));
@@ -205,6 +213,7 @@ export class UsageLimits {
205
213
  // Identity checks below keep late responses from restoring a replaced provider's usage.
206
214
  this.request = controller;
207
215
  this.attemptedAt = Date.now();
216
+ this.randomizeRefresh();
208
217
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
209
218
  timeout.unref();
210
219
  try {
@@ -237,14 +246,15 @@ export class UsageLimits {
237
246
  if (!this.ctx || !window) return null;
238
247
  const now = Date.now();
239
248
  const resetLabel = formatResetLabel(window.resetAt, now);
240
- if (width < (resetLabel.length || 1)) return null;
241
- const cells = Math.min(maxCells, Math.max(0, width - resetLabel.length));
249
+ // Reserve two columns for brackets so an empty bar still has clear edges.
250
+ if (width < (resetLabel.length || 1) + 3) return null;
251
+ const cells = Math.min(maxCells, Math.max(1, width - resetLabel.length - 2));
242
252
  const steps = Math.round(window.used / 100 * cells * CELL_STEPS);
243
253
  const filled = "⣿".repeat(Math.floor(steps / CELL_STEPS)) + PARTIAL_CELL_FILLS[steps % CELL_STEPS];
244
254
  const empty = "⠀".repeat(cells - Math.ceil(steps / CELL_STEPS));
245
255
  const stale = this.stale || (window.resetAt !== null && now >= window.resetAt);
246
256
  const color = stale ? "dim" : window.used >= 92 ? "error" : window.used >= 85 ? "warning" : "success";
247
257
  const bar = cells ? theme.fg(color, filled) + theme.fg("dim", empty) : "";
248
- return bar + theme.fg("dim", resetLabel);
258
+ return theme.fg("dim", "[") + bar + theme.fg("dim", "]") + theme.fg("dim", resetLabel);
249
259
  }
250
260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-minimal-footer",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "An opinionated minimal footer extension for Pi — clean status line with path, extension statuses, model, and context usage.",
5
5
  "keywords": [
6
6
  "pi-package",