gh-glance 0.13.0 → 0.13.2

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/index.mjs +51 -1
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.2] - 2026-09-07
11
+
12
+ ### Fixed
13
+
14
+ - **"Upgrade waiting for legacy quota reset" could never clear.** A pane holding
15
+ a pre-0.12.0 ledger with unsettled GraphQL charges waited on a deadline that
16
+ did not exist, so the message stayed for as long as that file was present --
17
+ weeks, in one case, long after the quota windows it was waiting for had ended.
18
+ Migrating such a ledger deliberately drops its budgets, because their numbers
19
+ came from `/rate_limit` and were never spendable capacity; the reset times
20
+ recorded alongside them are still facts about when those windows closed, and
21
+ are now read as the deadline they always were. Reading a reset is not the same
22
+ as spending against it. Affected panes clear themselves on the next launch.
23
+
24
+ ## [0.13.1] - 2026-09-07
25
+
26
+ ### Fixed
27
+
28
+ - **Upgrading from 0.12.0 no longer leaves every pane unable to coordinate.**
29
+ 0.13.0 moved the coordination protocol from version 3 to 5 but could migrate
30
+ only version 1, and unlike earlier protocol changes it kept the state file in
31
+ the same place. A pane upgrading therefore read its own file, could neither
32
+ validate nor migrate it, and reported unusable coordination state on every
33
+ launch from then on -- the read happens before any write, so nothing ever
34
+ replaced the file. Versions 2, 3 and 4 now upgrade in place, keeping the
35
+ unsettled charges and observed budgets they hold. A file from a *newer*
36
+ protocol is still refused, which is the recoverable direction: the newer build
37
+ is still there to rewrite it.
38
+
39
+ - **Diagnostics and manual actions no longer pace as though they spent their
40
+ worst case.** Work is admitted against the most it could cost, and the
41
+ difference is returned once the real cost is known. That return was wired
42
+ into only one of the two settlement paths, so every `--doctor` probe and
43
+ manual operation kept paying for capacity it had not used, delaying later
44
+ requests for no reason.
45
+
10
46
  ## [0.13.0] - 2026-09-06
11
47
 
12
48
  ### Changed
@@ -904,7 +940,9 @@ engineering, security, QA and UX. What follows is what changed as a result.
904
940
  - The `main` field from `package.json`. It advertised the file as importable,
905
941
  but importing it took over the terminal or exited the host process.
906
942
 
907
- [Unreleased]: https://github.com/juan294/gh-glance/compare/v0.13.0...HEAD
943
+ [Unreleased]: https://github.com/juan294/gh-glance/compare/v0.13.2...HEAD
944
+ [0.13.2]: https://github.com/juan294/gh-glance/compare/v0.13.1...v0.13.2
945
+ [0.13.1]: https://github.com/juan294/gh-glance/compare/v0.13.0...v0.13.1
908
946
  [0.13.0]: https://github.com/juan294/gh-glance/compare/v0.12.0...v0.13.0
909
947
  [0.12.0]: https://github.com/juan294/gh-glance/compare/v0.11.2...v0.12.0
910
948
  [0.11.2]: https://github.com/juan294/gh-glance/compare/v0.11.1...v0.11.2
package/index.mjs CHANGED
@@ -2528,7 +2528,19 @@ function inspectLegacyMigration(root, state, now) {
2528
2528
  const uncertain = Object.values(legacy.reservations).some((reservation) =>
2529
2529
  ["started", "completed"].includes(reservation.status) && reservationCost(reservation, resource, legacy.leases, now) > 0);
2530
2530
  if (uncertain) {
2531
- const reset = budget?.resetMs;
2531
+ // The deadline may only be read from the migrated view for resources the
2532
+ // migration kept. A v1 file's budgets are dropped on purpose -- they came
2533
+ // from /rate_limit and are not spendable capacity -- but the reset they
2534
+ // record is still a fact about when that window ended, and using it as a
2535
+ // deadline is not the same as admitting work against it. Core already had
2536
+ // a reconstruction above; GraphQL never did, so a v1 file holding any
2537
+ // uncertain GraphQL charge asked a dropped budget for its reset, found
2538
+ // none, and reported legacy-unresolved -- which has no deadline and so
2539
+ // never cleared. The file it came from had a perfectly good reset in it
2540
+ // the whole time.
2541
+ const reset = Number.isFinite(budget?.resetMs)
2542
+ ? budget.resetMs
2543
+ : Number(raw?.budgets?.[resource]?.resetMs);
2532
2544
  if (!Number.isFinite(reset)) return { ok: false, reason: "legacy-unresolved" };
2533
2545
  holdUntil = Math.max(holdUntil, reset + BUDGET_RESET_GRACE_MS);
2534
2546
  }
@@ -3510,7 +3522,36 @@ function serializeGovernorState(state) {
3510
3522
  return `${JSON.stringify(state)}\n`;
3511
3523
  }
3512
3524
 
3525
+ // Failing closed on a version this build does not write is right in one
3526
+ // direction only. An *older* binary meeting a newer file must refuse it, because
3527
+ // it cannot know what the new fields mean. A *newer* binary meeting an older
3528
+ // file at its own canonical path has to migrate it: the read happens before any
3529
+ // write, so refusing leaves the file in place and the pane never recovers. It is
3530
+ // not a transient failure, it is a permanent one, and every later launch repeats
3531
+ // it.
3532
+ //
3533
+ // That asymmetry was missed when the observer split moved the protocol from 3 to
3534
+ // 5. Versions 1 and 2 escaped it only because the file moved path at the same
3535
+ // time, so a new build simply found nothing and started clean; 3 onwards share a
3536
+ // path with 5 and collided.
3513
3537
  function migrateGovernorState(raw, nowMs) {
3538
+ if (!isRecord(raw) || !Number.isSafeInteger(raw.version) || raw.version < 1) return null;
3539
+ if (raw.version >= GOVERNOR_STATE_VERSION) return null;
3540
+ if (raw.version === 1) return migrateV1GovernorState(raw, nowMs);
3541
+ // Everything from 2 onwards is this build's shape or one adaptation away from
3542
+ // it, and what it holds is worth keeping: the uncertain reservations are debts
3543
+ // that must not be forgiven by an upgrade, and the budgets were written by the
3544
+ // same observers this build trusts.
3545
+ const shaped = raw.version >= GOVERNOR_OBSERVER_SPLIT_VERSION
3546
+ ? structuredClone(raw)
3547
+ : adaptPreSplitGovernorShape(raw);
3548
+ if (!shaped) return null;
3549
+ if (!isRecord(shaped.fairness)) shaped.fairness = { manualStreak: 0 };
3550
+ shaped.version = GOVERNOR_STATE_VERSION;
3551
+ return normalizeGovernorState(shaped, nowMs);
3552
+ }
3553
+
3554
+ function migrateV1GovernorState(raw, nowMs) {
3514
3555
  if (!exactKeys(raw, [
3515
3556
  "version", "epochs", "budgets", "probeClaim", "probeOutcome", "leases", // v1 shape
3516
3557
  "intents", "reservations", "manualProbe",
@@ -4478,6 +4519,15 @@ function completeReservation(scope, reservationId, completion, nowMs) {
4478
4519
  reservation.outcome = completion.outcome;
4479
4520
  reservation.actualCosts = { ...measured };
4480
4521
  reservation.accountedCosts = { core: 0, graphql: 0 };
4522
+ // The same rule as the observing settlement path: this one narrows the
4523
+ // charge in exactly the same way, so it releases the pacing in exactly the
4524
+ // same way. Wiring it into only one of the two settlement paths left every
4525
+ // doctor probe and manual operation paying for capacity it did not use.
4526
+ if (completion.outcome === "measured-success") {
4527
+ for (const resource of RATE_RESOURCES) {
4528
+ returnPacingCredit(state, resource, reservation.costs[resource] - measured[resource], at);
4529
+ }
4530
+ }
4481
4531
  return { value: { status: "completed", actualCosts: reservation.actualCosts } };
4482
4532
  });
4483
4533
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gh-glance",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "A live-refreshing GitHub dashboard for your terminal: Actions, Issues, Pull Requests, and Security alerts in one narrow pane",
5
5
  "type": "module",
6
6
  "bin": {