gh-glance 0.11.2 → 0.13.0

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 (4) hide show
  1. package/CHANGELOG.md +112 -1
  2. package/README.md +36 -13
  3. package/index.mjs +2104 -385
  4. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.0] - 2026-09-06
11
+
12
+ ### Changed
13
+
14
+ - **Upgrading requires restarting older panes, and may wait for a quota reset.**
15
+ The coordination protocol moved from version 3 to 5. A pane still running an
16
+ older build holds a lease this one refuses to coordinate against, so it must
17
+ be closed; if that pane left API charges it never settled, admission waits for
18
+ the affected quota window to reset before spending against a counter it cannot
19
+ account for. Both are conservative on purpose, and both clear on their own.
20
+ - **A slow or failed budget observer no longer holds up the other resource.**
21
+ Core and GraphQL are claimed, published and retried independently. What still
22
+ couples them is the shared transport permit and any account-wide hold, which
23
+ are enforced where they belong.
24
+
25
+ ### Added
26
+
27
+ - **Secondary rate limits back off on a bounded ladder.** A `Retry-After` the
28
+ server supplies is honoured exactly, in seconds or as an HTTP date, and
29
+ concurrent holds merge by maximum so a shorter error cannot shorten a longer
30
+ hold. Without a supplied deadline the client waits 60, 120, 240, 480 then 900
31
+ seconds by consecutive throttle; that cap applies only to the delay gh-glance
32
+ chooses, never to one the server asked for. After five consecutive throttles
33
+ it stops choosing and waits for an explicit refresh.
34
+ - **A run of manual refreshes yields a turn to a waiting tab.** Manual work
35
+ still outranks background polling, but after three consecutive grants an
36
+ active tab that has been waiting goes next, so holding `r` can no longer
37
+ starve the rest of the dashboard.
38
+
39
+ ### Fixed
40
+
41
+ - **A permission error no longer pauses every pane.** A 403 carrying no
42
+ rate-limit headers and no secondary-limit marker is a permission failure, not
43
+ a throttle. It previously held the shared transport, so one repository the
44
+ token could not read stalled every pane using that account.
45
+ - **The external-use estimator can come back down.** When other clients were
46
+ busy, gh-glance would raise its estimate of their share and then keep pacing
47
+ against it: each sampling window closed on publication even when the sample
48
+ was too small to reconcile, discarding the local accumulation that would
49
+ eventually have made one. A window now closes only when it actually
50
+ reconciles, and a new quota window starts from no assumed external use rather
51
+ than inheriting the previous window's ratio.
52
+ - **A request that cost nothing no longer paces as though it had.** Work is
53
+ paced by the cost it reserves, which is a worst case. A conditional request
54
+ answered `304` spends nothing, and cancelled work spends nothing at all -- the
55
+ unused pacing is now returned instead of leaving the next request waiting out
56
+ a slot nobody used.
57
+ - **Older coordination files are readable again.** The version 3 to 4 change
58
+ altered the file's shape, and the legacy reader recognised only the version
59
+ number, so every file an older pane had actually written was rejected as
60
+ corrupt -- exactly the file the restart boundary has to read to see that an
61
+ older pane is still running.
62
+
63
+ ## [0.12.0] - 2026-09-06
64
+
65
+ ### Added
66
+
67
+ - **Panes sharing a GitHub account now share one API budget.** Quota is keyed by
68
+ the verified principal and access by the effective credential, so two tokens
69
+ for the same account coordinate as one instead of splitting the budget in
70
+ half. A shared transport permit serializes HTTP starts across panes, and the
71
+ bootstrap allowance is persisted before a request starts, so a crash, restart
72
+ or manual refresh cannot replenish it.
73
+
74
+ ### Changed
75
+
76
+ - **Issues and Pull Requests use explicit, bounded GraphQL documents.**
77
+ `gh issue list` and `gh pr list` silently routed through GraphQL at a price
78
+ nothing in the codebase could name -- the budget was debited from a number
79
+ measured by hand once and written into a constant. Both now use fixed,
80
+ versioned documents with typed variables on stdin, so the query never reaches
81
+ argv and every page declares the cost it is held to.
82
+ - **`gh api rate_limit` is no longer a source of spendable GraphQL capacity.**
83
+ A claimed observer that selects only the meter supplies capacity and reports
84
+ what it cost. `/rate_limit` remains a diagnostic, is labelled as one in
85
+ `--doctor`, and is dropped on migration rather than kept as spendable
86
+ evidence.
87
+ - **Opening a row spends no API budget.** `gh <kind> view --web` paid a request
88
+ to be told a URL the app already held, and had to refuse the user the page
89
+ when the budget was tight. URLs are admitted -- https only, matching host --
90
+ before reaching the platform opener.
91
+
92
+ ### Fixed
93
+
94
+ - **Issues and Pull Requests worked only with an explicit `--repo`.** `gh`
95
+ infers the repository from the working directory and GraphQL cannot, so the
96
+ query variables went out half-filled without it.
97
+ - **Multi-page repositories leaked a tab reservation on every poll.** Settlement
98
+ summed all page costs even though later pages settle against their own
99
+ reservation, and the resulting over-settlement was rejected as corrupt --
100
+ discarding the budget observations from the only requests carrying real cost
101
+ evidence.
102
+ - **The budget observer's own spend was attributed to external consumers**, so
103
+ the governor progressively throttled real work to make room for its own
104
+ probing.
105
+ - **A contended registry lock could retract a verified identity**, blanking
106
+ every tab and discarding the ETags that make the next round cheap. Unknown
107
+ and changed are now distinct, and only a proven change discards rows.
108
+ - **Bootstrap attempts that never proved a principal were never retired**, so
109
+ the registry filled to its cap behind a captive portal and refused every
110
+ later bootstrap permanently, recoverable only by deleting state by hand.
111
+ - **Identity debt transfer wrote the ledger and registry non-atomically**, so a
112
+ crash could orphan a charged receipt or record a debt as transferred to a
113
+ ledger that never received it.
114
+ - **A stopped pane held the single machine-wide HTTP permit indefinitely**, as
115
+ it was reclaimed only on owner death.
116
+ - **The Open key hint was dead on every tab**, and opening a cache-hydrated row
117
+ did nothing because the cache dropped the row's URL.
118
+
10
119
  ## [0.11.2] - 2026-08-31
11
120
 
12
121
  ### Fixed
@@ -795,7 +904,9 @@ engineering, security, QA and UX. What follows is what changed as a result.
795
904
  - The `main` field from `package.json`. It advertised the file as importable,
796
905
  but importing it took over the terminal or exited the host process.
797
906
 
798
- [Unreleased]: https://github.com/juan294/gh-glance/compare/v0.11.2...HEAD
907
+ [Unreleased]: https://github.com/juan294/gh-glance/compare/v0.13.0...HEAD
908
+ [0.13.0]: https://github.com/juan294/gh-glance/compare/v0.12.0...v0.13.0
909
+ [0.12.0]: https://github.com/juan294/gh-glance/compare/v0.11.2...v0.12.0
799
910
  [0.11.2]: https://github.com/juan294/gh-glance/compare/v0.11.1...v0.11.2
800
911
  [0.11.1]: https://github.com/juan294/gh-glance/compare/v0.11.0...v0.11.1
801
912
  [0.11.0]: https://github.com/juan294/gh-glance/compare/v0.10.0...v0.11.0
package/README.md CHANGED
@@ -319,7 +319,7 @@ pane definition. Flags are there when you want them:
319
319
  | `-R`, `--repo [host/]owner/name` | Watch a specific repository instead of the current directory's. Works from anywhere -- you do not need a local clone. The optional host targets a GitHub Enterprise or EMU data-residency tenant, e.g. `tenant.ghe.com/acme/widget`. |
320
320
  | `--refresh <seconds>` | Minimum active-tab poll interval, 2-3600, default 5. Safe shared grants can make a check later; each background tab is considered about every 12 floors. |
321
321
  | `--tab <name>` | Start on `actions`, `issues`, `prs` or `security`. |
322
- | `--verbose` | Log one line per `gh` call to stderr, with timing and outcome. stderr must be redirected: `gh-glance --verbose 2>gh-glance.log`. |
322
+ | `--verbose` | Log one line per dashboard `gh` call to stderr, with timing and outcome. Credential lookup and the account-identity proof are deliberately excluded, so that they cannot log anything derived from a token. stderr must be redirected: `gh-glance --verbose 2>gh-glance.log`. |
323
323
  | `--doctor` | Print a diagnostic report and exit. See [Diagnostics](#diagnostics). |
324
324
 
325
325
  An unrecognised flag exits 2 rather than being ignored, so a typo fails loudly.
@@ -396,21 +396,39 @@ recovery state owned by the UI, not a configuration interface.
396
396
 
397
397
  ### Private API governor state
398
398
 
399
- API admission uses a separate file beside the cache:
399
+ API admission uses a separate directory beside the cache:
400
400
 
401
401
  ```text
402
- $XDG_CONFIG_HOME/gh-glance/rate-governor-v1-<scope hash>.json
403
- ~/Library/Application Support/gh-glance/rate-governor-v1-<scope hash>.json
404
- ~/.config/gh-glance/rate-governor-v1-<scope hash>.json
402
+ $XDG_CONFIG_HOME/gh-glance/coordination-v2/
403
+ ~/Library/Application Support/gh-glance/coordination-v2/
404
+ ~/.config/gh-glance/coordination-v2/
405
405
  ```
406
406
 
407
- The hash binds the effective GitHub host to the local `gh` account namespace
408
- without putting either raw value in the file name. The file records only the
409
- protocol needed to coordinate panes: REST and GraphQL budget observations and
410
- epochs, leases, intents, reservations, fair-lane cursors, probe ownership and
411
- outcomes, and temporary rate-limit blocks. It stores no token, raw host, login,
412
- repository, working directory, title, author, branch, alert, or other dashboard
413
- row. Ephemeral lock records contain only a PID and random nonce.
407
+ It holds `registry.json`, which maps credentials to verified accounts, and one
408
+ `quota-<hash>.json` ledger per account. The ledger hash binds the effective
409
+ GitHub host to the *verified principal* -- the account kind and numeric ID that
410
+ GitHub itself confirmed -- rather than to whichever local `gh` configuration
411
+ happened to select it, so two tokens for the same account coordinate as one
412
+ instead of splitting the budget in half.
413
+
414
+ A ledger records only the protocol needed to coordinate panes: REST and GraphQL
415
+ budget observations and epochs, leases, intents, reservations, fair-lane
416
+ cursors, probe ownership and outcomes, and temporary rate-limit blocks. It
417
+ stores no token, raw host, login, repository, working directory, title, author,
418
+ branch, alert, or other dashboard row.
419
+
420
+ `registry.json` is the one exception, and it is deliberate: to know that two
421
+ credentials are the same account it stores that account's numeric ID and its
422
+ sanitized login, alongside private digests of the credentials themselves. No
423
+ token is ever written, to it or anywhere else. Ephemeral lock records contain
424
+ only a PID and random nonce.
425
+
426
+ Panes from an older release coordinate through the previous
427
+ `rate-governor-v1-<scope hash>.json` files, which this version cannot join.
428
+ It preserves them as evidence, refuses to activate while any of their leases is
429
+ still live, and waits out any unsettled charge they left behind. Closing the
430
+ older panes is what completes the upgrade -- gh-glance will not close them for
431
+ you, and deleting the files instead discards quota that is still owed.
414
432
 
415
433
  This state has a stricter role than `dashboard-cache.json`. A missing cache can
416
434
  be ignored because it only supplies last-good rows. Missing governor state can
@@ -692,7 +710,12 @@ GH_GLANCE_ICONS=ascii gh-glance
692
710
  | Cached rows plus `Paused` and `stale 2m` | The rows came from the separate last-known-good dashboard cache, while the live request is blocked or unsafe. Stale age is not extended by a pause. The error and footer describe current coordination; cached data never means the live check succeeded. |
693
711
  | Repeated GitHub rate-limit messages | A classified rate-limit response is published as one shared resource block. Local panes make no data retry before that block's probe/reset deadline. Use `--doctor` to inspect the resource and reset; repeated manual refresh cannot override it. |
694
712
  | It exits immediately when piped | Intentional. It is a full-screen dashboard, not a reporting command. |
695
- | It stopped updating and you cannot tell why | Run `gh-glance --verbose 2>gh-glance.log`, reproduce, then read the log: one line per `gh` call with its duration and outcome. It is redacted the same way `--doctor` is, so it is safe to attach to a bug report. `--doctor --verbose` logs the probes too. |
713
+ | `Restart required: close older gh-glance panes` | A pane from an older release still holds a live lease in the previous coordination format, which this version cannot join. Quit those panes; this one resumes on its own. Nothing is killed for you, and no state needs deleting. |
714
+ | `Upgrade waiting for legacy quota reset` | The older panes are gone, but left spend that cannot be proven settled. It waits for the affected rate-limit window to reset rather than assume the quota is free. |
715
+ | `Coordination state unavailable; evidence preserved` | Coordination state could not be read and was deliberately not overwritten, so nothing is lost. Run `gh-glance --doctor` and check the config directory's permissions. |
716
+ | `Shared HTTP request or cooldown in progress` | Panes share one outbound request slot and one cooldown. Another pane holds it, or a server-sent `Retry-After` is still running. Normal pacing, not an error. |
717
+ | `Verified identity unavailable; waiting to retry` | The account behind your credential has not been confirmed yet -- not signed in, a denied or malformed proof, or a slow credential helper. Check `gh auth status`. Retries are rate-limited, so this clears on its own rather than immediately. |
718
+ | It stopped updating and you cannot tell why | Run `gh-glance --verbose 2>gh-glance.log`, reproduce, then read the log: one line per dashboard `gh` call with its duration and outcome. Credential lookup and the identity proof are excluded by design. It is redacted the same way `--doctor` is, so it is safe to attach to a bug report. `--doctor --verbose` logs the probes too. |
696
719
  | `--verbose` refuses to start | stderr is still your terminal, where the log would draw over the dashboard. Redirect it to a file. |
697
720
 
698
721
  ## Contributing