gh-glance 0.4.1 → 0.5.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 +188 -0
  2. package/README.md +85 -39
  3. package/index.mjs +606 -112
  4. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,193 @@ All notable changes to this project are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2026-08-04
9
+
10
+ Findings from a cross-functional audit, filtered hard: everything that would
11
+ have added a release gate, a branch-protection rule or more test-harness
12
+ machinery was rejected outright. What is left is behaviour you can see.
13
+
14
+ ### Added
15
+
16
+ - **`?` shows the keys without leaving the dashboard.** Paging, `j`/`k`,
17
+ `Shift+Tab` and the cursor timeout existed only in `--help` and the README --
18
+ and because this is a full-screen app, reading `--help` meant quitting it
19
+ first. Any key closes the overlay. `--help`, the overlay and the status-bar
20
+ hints are now generated from one table, so a binding cannot go missing from one
21
+ of them.
22
+ - **The panel names the repository** when it was chosen explicitly through
23
+ `--repo` or `GH_REPO` -- `╭─ Actions · acme/widget ─`. With several panes open,
24
+ or after changing directory, nothing on screen said which repository a pane was
25
+ watching. Dropped before the tab name when the pane is too narrow for both.
26
+ - **`--doctor` reports API budget**: REST and GraphQL headroom straight from
27
+ GitHub, plus what this configuration will actually spend per hour. The steady
28
+ cost is not small and was invisible -- roughly 2,200 REST requests an hour at
29
+ the default refresh with the Security tab open, about 44% of a personal token's
30
+ allowance, and `--refresh 2` projects past the limit outright. `gh api
31
+ rate_limit` does not itself count against the limit.
32
+ - **Native test coverage is measured and reported.** `npm run test:coverage`
33
+ runs the unit suite under Node's built-in coverage, and a nightly workflow
34
+ publishes the figure to the portfolio endpoint. The delivery script fails
35
+ closed on purpose: every input is required and a parse failure is an error
36
+ rather than a reported zero, so a broken producer cannot quietly claim perfect
37
+ coverage. Not wired into branch protection -- it reports, it does not gate.
38
+ - **A first-run hint when icons may be blank.** Without a Nerd Font every status
39
+ icon renders as an empty box, which reads as a broken program rather than a
40
+ missing font, and the fix was documented only in places you had to quit to
41
+ read. Shown for the second the first fetch takes, and only when the Nerd glyphs
42
+ are actually in use.
43
+
44
+ ### Fixed
45
+
46
+ - **The `stale` indicator no longer fires on a repository that is simply quiet.**
47
+ Freshness was recorded only when the payload *changed*, and the whole point of
48
+ the poll loop is that an unchanged payload short-circuits before that write --
49
+ so on any calm repo the timestamp froze and the status bar accrued a growing
50
+ `stale 2h13m` while every poll was succeeding on schedule. It now records the
51
+ last successful *poll*. The warning fired loudest in the one state that was
52
+ completely healthy, which is the fastest way to teach someone to ignore it.
53
+ - **A tab whose fetch keeps failing no longer animates forever.** `setData` only
54
+ runs on success, so a tab that never succeeded stayed in its first-load state
55
+ permanently: the spinner ran at full rate for the life of the process while the
56
+ body rendered "loading actions…" directly above the error explaining that it had
57
+ failed. Measured at 7.8% of a core and 9.8 MB/hr of terminal writes,
58
+ indefinitely, triggered by something as ordinary as closing a laptop lid.
59
+ Motion now means "still working"; the error line means "not working".
60
+ - **`Enter` is guarded against key repeat.** Holding it down spawned a
61
+ `gh <kind> view --web` per repeat event -- roughly thirty a second -- each
62
+ opening another browser tab. The `r` key beside it had this guard all along.
63
+ - **The status bar adapts to narrow panes.** It was the only band that did not:
64
+ the table swaps to a compact header, the tab bar to short labels, the panel
65
+ edges drop their labels, and the status bar just let ink truncate. Because each
66
+ hint truncates individually the loss was silent -- at 45 columns the bar read
67
+ `Move: | Open: | Refresh |Quit:…`, dropping the arrows, `Refresh`'s key, and
68
+ most of `Quit`, which is the one hint someone stuck in a full-screen app needs.
69
+ It now falls back to `↑↓ Ent r q`, which keeps every key.
70
+ - **`--doctor` no longer prints the value of environment variables it was never
71
+ told about.** It discovers every `GH_*`/`GITHUB_*` variable that is set, and
72
+ printed the value of any whose *name* did not end in `_TOKEN`/`_SECRET`/
73
+ `_PASSWORD`/`_KEY` -- so `GH_APP_PEM` (an entire RSA private key) and
74
+ `GITHUB_OAUTH` were reproduced in full, in the one report that says it is safe
75
+ to paste into a bug report. Printing a value is now opt-in and the list is
76
+ curated; anything discovered reports `set` or `not set`.
77
+ - **`--doctor --verbose` produces the log it advertises.** argv was applied to
78
+ runtime state in two places and they had drifted, so the doctor path silently
79
+ dropped `--verbose`, `--refresh` and `--tab`. There is one application site now.
80
+ - **The 60s cursor clear no longer costs you your place.** Clearing the selection
81
+ left the scroll offset behind, and the next arrow key seeded from the top of the
82
+ list -- so scrolling to row 80 of 150, reading for a minute and pressing down
83
+ put you back at row 1, with no scroll animation to notice. Movement now seeds
84
+ from what is on screen.
85
+ - **`safe()` sanitizes non-string input.** It returned early for anything that
86
+ was not a string, skipping both the control-character strip and the length
87
+ clamp -- the guarantee was being provided by GitHub's schema rather than by the
88
+ function that claims to provide it.
89
+ - **The `!` "CI is red" marker stays in the tab bar**, where a label anchors it,
90
+ instead of also being interpolated into the frame's bottom edge as `4 of 4!`.
91
+ - **A blind Security tab no longer reports a confident zero.** When all three
92
+ alert endpoints fail -- an expired SAML session, a token without
93
+ `security_events`, an org OAuth restriction -- the tab rendered
94
+ `4:Security (0)`, byte-identical to a genuinely clean repository, on the one
95
+ surface where a false all-clear is the worst available answer. It now reads
96
+ `(?)`. A repository that simply has Advanced Security switched off is a
97
+ different thing and stays quiet, because that is an answer rather than an
98
+ absence of one.
99
+ - **A failing tab backs off instead of re-spawning `gh` every tick.** The three
100
+ list tabs had no backoff at all, so a wedged tab launched a subprocess every
101
+ five seconds indefinitely -- 720 an hour against a token already refusing --
102
+ and rate limiting was deliberately excluded from the ladder that did exist,
103
+ which had it backwards: GitHub's secondary limiter keys on sustained rate
104
+ against a limited *token*, so hammering can turn a self-clearing limit into a
105
+ longer block that also hits `git push`. Measured: two `gh run list` calls in
106
+ forty seconds where there were eight. The `r` key bypasses and clears the
107
+ backoff, because a refresh that silently declines to refresh would be worse
108
+ than no key at all.
109
+ - **Errors on the Actions, Issues and PR tabs say what to do.** They rendered raw
110
+ `gh` stderr while the Security tab one across had been classifying the same
111
+ failures into remedies all along. An expired token now reads "GitHub
112
+ authorization failed -- try `gh auth login` or `gh auth refresh`". Unclassified
113
+ failures still show their real message: inventing a remedy for something
114
+ nobody recognised would be worse than showing what happened.
115
+ - **Bidi override characters are stripped from remote text.** `U+202A`-`U+202E`
116
+ and the isolates measure as zero columns, so they cost no width and survived
117
+ truncation -- one `RLO` in an issue title makes the rest of that cell render
118
+ reversed on any terminal with bidi reordering, which is the same "the row does
119
+ not show its data" failure the control-character strip already prevents.
120
+ Deleted rather than replaced with a space, because they measure zero and a
121
+ space would shift every cell to its right. Genuine RTL, `LRM`, emoji, ZWJ
122
+ sequences and CJK are untouched.
123
+ - **`REPO_PATTERN` rejects `owner/..`.** It reached the API path, and `gh`
124
+ forwards the dot segment unnormalized, so GitHub resolved it to a different
125
+ endpoint than the one intended. Names that merely contain or start with a dot
126
+ -- `owner/.github` -- are still valid.
127
+ - **Backoff deadlines use a monotonic clock.** They measure elapsed time, and a
128
+ laptop resume or an NTP step could hold an alert source in backoff for an hour
129
+ of apparent time that never passed. Staleness deliberately stays on wall-clock
130
+ time, because a sleep gap is exactly what it exists to report.
131
+ - **A crash no longer orphans `gh` children.** The handlers exited without
132
+ unmounting, so the cleanup that aborts in-flight subprocesses never ran. Both
133
+ the crash output and the `--verbose` log are now redacted, like `--doctor`
134
+ already was -- all three are artifacts users are told to attach to bug reports,
135
+ and `gh` error messages quote the URL they failed on.
136
+
137
+ ### Changed
138
+
139
+ - **The spinner runs at 200ms rather than 100ms.** It is the single largest CPU
140
+ term in the app, because every frame makes ink rebuild and diff the whole
141
+ output string: measured 7.8% of a core at 100ms against 3.9% at 200ms, on a
142
+ 0.33% idle floor. The motion is load-bearing -- it is the only thing separating
143
+ an executing run from a queued one -- so it is slowed, never stopped.
144
+ - **Only an executing Actions row receives the spinner frame.** It went to every
145
+ visible row, so a prop changing several times a second defeated row
146
+ memoisation for all of them: 7,960 row renders over 20 seconds against 238.
147
+ - **The panel names the repository** when it was chosen explicitly via `--repo`
148
+ or `GH_REPO` -- `╭─ Actions · acme/widget ─`. With several panes open, or after
149
+ changing directory, nothing on screen said which repository a pane was watching.
150
+ It is dropped before the tab name when the pane is too narrow for both.
151
+ - **Column density is decided per tab.** One global breakpoint meant the widest
152
+ tab decided for the narrowest: Pull requests needs 61 columns and Security only
153
+ 44, so Security dropped two columns a full 17 columns before it had to. Only
154
+ one tab is on screen at a time, so there is nothing to be inconsistent with.
155
+ - **Below 24 columns the pane says "too narrow"** instead of rendering a table
156
+ that cannot fit. Under that width even the compact columns overflow, which
157
+ hard-wraps every row and drives ink into repainting the whole screen each
158
+ frame -- reachable just by dragging a sidebar narrow. Widening recovers
159
+ immediately.
160
+ - **The Security tab collapses its not-enabled notes into one line.** Each
161
+ unavailable alert source took a full row above the column header, so any
162
+ repository without Advanced Security permanently spent two rows -- about a
163
+ tenth of a twenty-row pane -- restating a fact that will never change, on the
164
+ tab meant to make real alerts stand out. Failures that are not "not enabled"
165
+ keep their own lines, because those are actionable and transient.
166
+ - **Alert ordering is pinned rather than left to each endpoint's default.**
167
+ Severity ranking runs over one page, so the page boundary decides what can be
168
+ ranked at all -- a critical alert sitting past it was never fetched, and the
169
+ pane showed "100+" with a screen of moderates. Newest-first at least makes the
170
+ cut deterministic.
171
+ - **`-v` is no longer an alias for `--version`.** This CLI also has `--verbose`,
172
+ so `gh-glance -v 2>log` -- what you type when you want the log -- printed a
173
+ version string and exited 0. The argv surface exists to make typos fail loudly,
174
+ and this was the one flag that failed quietly. `--version` is unaffected.
175
+
176
+ ### Documentation
177
+
178
+ - **Corrected a claim this project made about itself in 0.4.0 and repeated in the
179
+ 0.4.1 release notes.** The status bar's `↑↓` glyphs were described as "a
180
+ deliberate, tested exception" whose double-width rendering "fails loudly" under
181
+ `npm run test:pty`. No such assertion exists or could: each hint truncates
182
+ individually, so the failure mode is silent text loss rather than overflow, and
183
+ at 80 columns the panel border is 79 cells against a 54-cell status bar, so the
184
+ bar never sets the maximum a width check would measure. The glyphs are fine;
185
+ the guarantee was not real, and a comment asserting coverage that does not
186
+ exist is worse than an acknowledged gap.
187
+ - The README no longer claims every run state has a distinct glyph under
188
+ `NO_COLOR`. Timed-out, action-required and running share one, as do skipped,
189
+ neutral, stale and queued. The tab bar's `!` marker is what actually answers
190
+ "is CI red" without colour, and it does.
191
+ - `eslint.config.js` no longer describes an inline `exhaustive-deps` suppression
192
+ that has never existed; the rule is now an error, which it can be precisely
193
+ because there is nothing to suppress.
194
+
8
195
  ## [0.4.1] - 2026-08-04
9
196
 
10
197
  Two fixes to the interactive surface: a selection marker that outlived its
@@ -329,6 +516,7 @@ engineering, security, QA and UX. What follows is what changed as a result.
329
516
  - The `main` field from `package.json`. It advertised the file as importable,
330
517
  but importing it took over the terminal or exited the host process.
331
518
 
519
+ [0.5.0]: https://github.com/juan294/gh-glance/releases/tag/v0.5.0
332
520
  [0.4.1]: https://github.com/juan294/gh-glance/releases/tag/v0.4.1
333
521
  [0.4.0]: https://github.com/juan294/gh-glance/releases/tag/v0.4.0
334
522
  [0.3.1]: https://github.com/juan294/gh-glance/releases/tag/v0.3.1
package/README.md CHANGED
@@ -37,10 +37,12 @@ without switching to the browser.
37
37
  > status icons render in a browser. With a Nerd Font (the default) they are
38
38
  > Octicon glyphs instead.
39
39
  >
40
- > The `>` on the first row is the cursor. Two more markers appear when they
40
+ > The `>` on the first row is the cursor. Three more markers appear when they
41
41
  > apply: a `+` after a tab's count means the list was truncated by the fetch
42
- > limit, and a `!` after the Actions count means the newest run failed — so
43
- > "is CI red" is answerable without switching to that tab.
42
+ > limit, a `!` after the Actions count means the newest run failed — so "is CI
43
+ > red" is answerable without switching to that tab — and a `?` in place of the
44
+ > Security count means its endpoints could not be read, which is not the same
45
+ > as there being nothing to report.
44
46
 
45
47
  <!-- contract:allow-emoji -- the check/cross above stand in for Nerd Font
46
48
  Octicons the app actually draws; they are literal examples, not decoration. -->
@@ -64,20 +66,26 @@ current.
64
66
  (approved / changes requested / pending), age
65
67
  - **Security** -- open Dependabot alerts: package, summary, age, sorted with the
66
68
  most severe first and with severity spelled out in its own column, not carried
67
- by colour alone.
69
+ by colour alone. One page of 100 newest alerts is fetched and ranked, so on a
70
+ repository with more open alerts than that the ranking covers the newest 100
71
+ rather than everything -- the `+` marker on the count is what tells you.
68
72
  Code scanning and secret scanning alerts are included too, on repos/plans
69
73
  that have GitHub Advanced Security enabled -- see [Limitations](#limitations).
70
74
  - Tab bar with live counts, pinned to the top of the pane above a divider,
71
75
  switchable via `1`-`4`, arrow keys, or `Tab`/`Shift+Tab`
72
76
  - `lazygit`-style panel frame: the tab name sits in the top border, the
73
77
  visible-of-total row count in the bottom
74
- - A spinning `Fetching` indicator while a refresh is in flight, so the pane
75
- says when it's working without spending a line on it
78
+ - A `Fetching` indicator that brightens on every refresh and animates during the
79
+ first load or while a workflow run is executing, so the pane says when it's
80
+ working without spending a line on it
76
81
  - Status icons are real GitHub Octicons (via the Nerd Font glyph set), not emoji
77
82
  -- with a plain-ASCII fallback for terminals without one
78
- - Readable without colour: severity has its own column, run states have distinct
79
- glyphs, and the active tab is bracketed, so `NO_COLOR=1` and colour-vision
80
- deficiency both stay navigable
83
+ - Readable without colour: severity has its own column, a failing newest run puts
84
+ a `!` on the Actions tab, and the active tab is bracketed, so `NO_COLOR=1` and
85
+ colour-vision deficiency both stay navigable. Run-state *glyphs* are not all
86
+ distinct -- timed-out, action-required and running share one, as do skipped,
87
+ neutral, stale and queued -- so the tab marker, not the row icon, is what
88
+ answers "is CI red" without colour
81
89
  - Says when it is stale rather than showing old data silently, and marks a tab
82
90
  whose last refresh failed
83
91
  - Adapts row count to the terminal pane's height live, on resize
@@ -187,13 +195,19 @@ stdout isn't a terminal rather than streaming redraw frames into a pipe.
187
195
  | `PgUp` / `PgDn` | Move a page at a time |
188
196
  | `Enter` | Open the selected item in your browser |
189
197
  | `r` | Refresh the current tab now |
198
+ | `?` | Show the keys without leaving the dashboard (any key closes it) |
190
199
  | `q` / `Esc` / `Ctrl+C` | Quit |
191
200
 
192
201
  The cursor tracks the *item*, not the row position, so it stays on what you
193
202
  selected as new rows arrive above it. `Enter` works on Actions, Issues and Pull
194
203
  Requests; the Security tab has no per-alert `gh` command to open. It clears
195
204
  itself after 60s with no cursor movement, so a pane left idle in the corner of
196
- a screen doesn't sit there marked forever.
205
+ a screen doesn't sit there marked forever -- and the next arrow key picks up
206
+ from what is on screen rather than jumping back to the top of the list.
207
+
208
+ When the repository was chosen explicitly, with `--repo` or `GH_REPO`, the panel
209
+ says so: `╭─ Actions · acme/widget ─`. That is what tells two side-by-side panes
210
+ apart. It is dropped before the tab name when the pane is too narrow for both.
197
211
 
198
212
  If `gh-glance` is started without an interactive stdin (for example with stdin
199
213
  redirected), the key handlers cannot run and the status bar shows only `Quit: ^C`
@@ -223,7 +237,7 @@ Environment variables work too, and the flags take precedence:
223
237
  | `GH_TOKEN`, `GH_ENTERPRISE_TOKEN`, `GH_CONFIG_DIR` | Not read by `gh-glance` -- passed through to `gh` untouched, along with the proxy variables. `gh-glance` handles no credentials of its own |
224
238
  | `GH_GLANCE_ICONS=unicode` | Plain ASCII status icons, for terminals without a Nerd Font |
225
239
  | `GH_GLANCE_NO_ANIMATION=1` | Freeze the spinner — no motion at all |
226
- | `NO_COLOR=1` | Disable colour. Status stays readable: severity has its own column, run states have distinct glyphs, and the active tab is bracketed |
240
+ | `NO_COLOR=1` | Disable colour. Status stays readable: severity has its own column, a failing newest run puts a `!` on the Actions tab, and the active tab is bracketed. Note that run-state glyphs are not all distinct -- see the feature list above |
227
241
  | `INK_SCREEN_READER=true` | Switch the renderer to a linear, unthrottled mode. The status icons carry text labels for it, but this path has not been tested against a real screen reader — treat it as unverified rather than supported. |
228
242
 
229
243
  ## GitHub Enterprise and EMU
@@ -267,36 +281,55 @@ gh-glance --doctor > report.txt
267
281
 
268
282
  Collects, in one plain-text block: the `gh-glance`, Node and `gh` versions;
269
283
  which hosts `gh` is authenticated for; how the repository target resolved and
270
- from where; the relevant environment variables; and one probe per endpoint with
271
- the exact argv it sent, its outcome, and how any error was classified
272
- (`unavailable`, `rate-limited`, `auth-problem` or `other`).
284
+ from where; your remaining REST and GraphQL budget plus what this configuration
285
+ will spend per hour; the relevant environment variables; and one probe per
286
+ endpoint with the exact argv it sent, its outcome, and how any error was
287
+ classified (`unavailable`, `rate-limited`, `auth-problem` or `other`).
288
+
289
+ Add `--verbose` to get a log of every `gh` call it makes alongside the report.
273
290
 
274
291
  It exits 0 and prints a report even when `gh` is missing or you are outside a
275
292
  git repository -- those are conditions worth reporting rather than failing on --
276
293
  and it works through a pipe, unlike the dashboard itself.
277
294
 
278
- **Tokens are never printed.** Token-valued environment variables are reported
279
- as `set` or `not set` and never by value, not even a prefix; anything
280
- token-shaped anywhere in the captured text is replaced; proxy and remote URL
281
- credentials are stripped to scheme and host; and no API response bodies are
282
- included, only their sizes. The report is safe to attach to a bug report.
295
+ **Values are printed only for variables on a short curated list** -- the ones
296
+ that are the thing being diagnosed, like `GH_HOST`, `GH_REPO` and `NO_COLOR`.
297
+ Everything else it finds, including any `GH_*`/`GITHUB_*` variable it was never
298
+ told about, is reported as `set` or `not set` and never by value. On top of
299
+ that: anything token-shaped anywhere in the captured text is replaced, proxy and
300
+ remote URL credentials are stripped to scheme and host, and no API response
301
+ bodies are included, only their sizes. The report is safe to attach to a bug
302
+ report, and the same redaction covers the `--verbose` log and the message
303
+ printed if `gh-glance` crashes.
283
304
 
284
305
  ## Rate limit
285
306
 
286
307
  The visible tab refreshes every 5 seconds; the other three refresh every 60
287
308
  seconds, purely to keep their counts honest.
288
309
 
289
- With Actions, Issues or Pull Requests as the visible tab that works out at
290
- roughly 1,000 REST requests an hour — about 20% of the 5,000/hour
291
- authenticated limit. The Security tab is the expensive one, because it is
292
- three endpoints rather than one: watching it costs roughly 2,300 an hour,
293
- just under half the budget.
294
-
295
- Two things pull the real number down. An endpoint that is not enabled for the
296
- repository — code scanning and secret scanning without Advanced Security —
297
- backs off after its first refusal rather than being re-asked every tick, which
298
- removes two of the Security tab's three calls. And `--refresh` scales the
299
- whole figure: doubling the interval halves it.
310
+ The tabs do not all draw on the same budget. Actions and Security spend REST
311
+ calls; Issues and Pull Requests are sorted with `--search`, which routes them
312
+ through GraphQL instead — a separate 5,000/hour allowance. With the default
313
+ 5-second refresh:
314
+
315
+ | Visible tab | REST / hour | GraphQL / hour |
316
+ |---|---|---|
317
+ | Actions | ~900 | ~240 |
318
+ | Issues or Pull Requests | ~240 | ~1,560 |
319
+ | Security | ~2,220 | ~240 |
320
+
321
+ Security is the expensive one because it is three endpoints rather than one,
322
+ and watching it costs just under half the REST budget. You do not have to take
323
+ these figures on trust: `gh-glance --doctor` reports your actual remaining
324
+ budget alongside what your configuration projects, which matters on a GitHub
325
+ Enterprise tenant where the ceiling may not be 5,000 at all.
326
+
327
+ Two things pull the real number down. Any endpoint that fails in a way
328
+ `gh-glance` recognises backs off instead of being re-asked every tick — a
329
+ feature that is not enabled for the repository retries on a ladder up to an
330
+ hour, an authorization failure every 30 seconds, a rate limit every minute.
331
+ Pressing `r` clears that backoff and retries immediately. And `--refresh`
332
+ scales the whole figure: doubling the interval halves it.
300
333
 
301
334
  So a pane left open all day shares the budget with your other `gh` commands
302
335
  rather than exhausting it, but it is not free, and parking it on the Security
@@ -308,19 +341,29 @@ tab is the case worth knowing about.
308
341
  - **Security tab**: code scanning and secret scanning alerts require [GitHub
309
342
  Advanced Security](https://docs.github.com/en/code-security/getting-started/github-security-features).
310
343
  On repos/plans without it, `gh-glance` doesn't crash or spam errors -- it
311
- shows a one-line note per unavailable feature and still displays whatever
312
- it *can* get (Dependabot alerts work independently of GHAS). If your repo
313
- has GHAS enabled, those alerts just show up automatically -- no
314
- configuration needed.
344
+ says so in a single collapsed line -- `Code scanning, Secret scanning: not
345
+ enabled here` -- and still displays whatever it *can* get (Dependabot alerts
346
+ work independently of GHAS). If your repo has GHAS enabled, those alerts just
347
+ show up automatically -- no configuration needed.
348
+
349
+ If the alert endpoints cannot be read at all -- an expired SAML session, a
350
+ token without `security_events`, an org OAuth restriction -- the tab count
351
+ shows `?` rather than a number. A blind Security tab and a clean one are not
352
+ the same thing, and reporting `0` for both would be the worst available
353
+ answer on that particular surface.
315
354
  - Scrolling moves through what was fetched, not through everything on GitHub.
316
355
  Issues and Pull Requests fetch 150, so there is real range there. Actions
317
356
  deliberately fetches about a screenful -- its cost is linear in the number of
318
357
  runs requested -- so scrolling that tab has little to move through. The count
319
358
  in the bottom edge always says how much you are seeing out of how much was
320
359
  fetched.
321
- - **Minimum width.** Below about 61 columns the table drops to a compact layout
322
- (icon, title, and one other column) so the frame, tab bar and status line stay
323
- on screen. Below about 24 columns it will not lay out sensibly.
360
+ - **Minimum width.** Each tab drops to a compact layout (icon, title, and one
361
+ other column) at its own threshold, so a narrow pane keeps as much as that
362
+ particular tab can fit: Security holds its full set down to 44 columns,
363
+ Issues to 50, Actions to 56, Pull Requests to 61. Below 24 columns nothing
364
+ useful fits, so the pane says `too narrow` and keeps the frame, tab bar and
365
+ quit hint; widening it recovers immediately. The status bar has its own
366
+ breakpoint and drops to bare keys (`↑↓ Ent r q`) rather than truncating.
324
367
  - Issues and pull requests are fetched 150 at a time and alerts 100 at a time.
325
368
  A count is shown as `n+` when it was truncated, so the number is never
326
369
  presented as exact when it is not.
@@ -363,10 +406,13 @@ in some terminals, which would shift every column to their right.
363
406
  | `none of the git remotes ... point to a known GitHub host` | `gh` is not authenticated for that host. Run `gh auth login --hostname <host>`. |
364
407
  | Security tab empty on an enterprise host, other tabs fine | The alert endpoints were sent to the wrong host. Use `--repo host/owner/name` or set `GH_HOST` -- a host-qualified `GH_REPO` alone does not route them. |
365
408
  | Tabs start failing after working for a while | The enterprise SAML session lapsed. Re-authorize in the browser; the dashboard recovers within about 30 seconds. Run `gh-glance --doctor` to confirm. |
366
- | A tab's count is red | That tab's last fetch failed. The error itself is shown when you switch to it. |
409
+ | A tab's count is red | That tab's last fetch failed. The error itself is shown when you switch to it, translated into what to do about it where `gh-glance` recognises the failure. |
410
+ | Security tab shows `?` instead of a number | The alert endpoints could not be read at all -- an expired SAML session, a token without `security_events`, or an org OAuth restriction. `?` means "unknown", not "zero"; run `gh-glance --doctor` to see which probe failed and how it was classified. |
411
+ | A failing tab seems to have stopped retrying | It backs off deliberately, rather than re-spawning `gh` every five seconds against an endpoint that is refusing. Press `r` to clear the backoff and retry now. |
412
+ | `unknown argument: -v` | `-v` used to mean `--version` and no longer does, because this CLI also has `--verbose`. Use `--version` or `--verbose` explicitly. |
367
413
  | `stale 2m` in the status bar | The visible tab has not refreshed successfully for a while — usually a network drop. |
368
414
  | It exits immediately when piped | Intentional. It is a full-screen dashboard, not a reporting command. |
369
- | 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. Attach it to a bug report. |
415
+ | 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. |
370
416
  | `--verbose` refuses to start | stderr is still your terminal, where the log would draw over the dashboard. Redirect it to a file. |
371
417
 
372
418
  ## Contributing