gh-glance 0.4.1 → 0.5.1

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 +204 -0
  2. package/README.md +85 -39
  3. package/index.mjs +640 -112
  4. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,208 @@ 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.1] - 2026-08-04
9
+
10
+ ### Changed
11
+
12
+ - **The blank-icons hint waits for a start that is actually stuck.** It shipped
13
+ in 0.5.0 on the loading line unconditionally, so every start put a line naming
14
+ an environment variable in front of everyone for the second or two the first
15
+ fetch takes -- a dim `loading actions…` reads as "working", the same line with
16
+ a remedy attached reads as a warning about a problem you do not have. It now
17
+ appears only once a first fetch has been running 3s, which is past the slow end
18
+ of what `gh run list` costs on the tab you land on (measured: 1.4-3.0s after
19
+ the first frame, against 0.6-1.1s for the other three tabs). An ordinary start
20
+ never shows it; a pane of blank boxes that is genuinely waiting still explains
21
+ itself.
22
+
23
+ ## [0.5.0] - 2026-08-04
24
+
25
+ Findings from a cross-functional audit, filtered hard: everything that would
26
+ have added a release gate, a branch-protection rule or more test-harness
27
+ machinery was rejected outright. What is left is behaviour you can see.
28
+
29
+ ### Added
30
+
31
+ - **`?` shows the keys without leaving the dashboard.** Paging, `j`/`k`,
32
+ `Shift+Tab` and the cursor timeout existed only in `--help` and the README --
33
+ and because this is a full-screen app, reading `--help` meant quitting it
34
+ first. Any key closes the overlay. `--help`, the overlay and the status-bar
35
+ hints are now generated from one table, so a binding cannot go missing from one
36
+ of them.
37
+ - **The panel names the repository** when it was chosen explicitly through
38
+ `--repo` or `GH_REPO` -- `╭─ Actions · acme/widget ─`. With several panes open,
39
+ or after changing directory, nothing on screen said which repository a pane was
40
+ watching. Dropped before the tab name when the pane is too narrow for both.
41
+ - **`--doctor` reports API budget**: REST and GraphQL headroom straight from
42
+ GitHub, plus what this configuration will actually spend per hour. The steady
43
+ cost is not small and was invisible -- roughly 2,200 REST requests an hour at
44
+ the default refresh with the Security tab open, about 44% of a personal token's
45
+ allowance, and `--refresh 2` projects past the limit outright. `gh api
46
+ rate_limit` does not itself count against the limit.
47
+ - **Native test coverage is measured and reported.** `npm run test:coverage`
48
+ runs the unit suite under Node's built-in coverage, and a nightly workflow
49
+ publishes the figure to the portfolio endpoint. The delivery script fails
50
+ closed on purpose: every input is required and a parse failure is an error
51
+ rather than a reported zero, so a broken producer cannot quietly claim perfect
52
+ coverage. Not wired into branch protection -- it reports, it does not gate.
53
+ - **A first-run hint when icons may be blank.** Without a Nerd Font every status
54
+ icon renders as an empty box, which reads as a broken program rather than a
55
+ missing font, and the fix was documented only in places you had to quit to
56
+ read. Shown for the second the first fetch takes, and only when the Nerd glyphs
57
+ are actually in use.
58
+
59
+ ### Fixed
60
+
61
+ - **The `stale` indicator no longer fires on a repository that is simply quiet.**
62
+ Freshness was recorded only when the payload *changed*, and the whole point of
63
+ the poll loop is that an unchanged payload short-circuits before that write --
64
+ so on any calm repo the timestamp froze and the status bar accrued a growing
65
+ `stale 2h13m` while every poll was succeeding on schedule. It now records the
66
+ last successful *poll*. The warning fired loudest in the one state that was
67
+ completely healthy, which is the fastest way to teach someone to ignore it.
68
+ - **A tab whose fetch keeps failing no longer animates forever.** `setData` only
69
+ runs on success, so a tab that never succeeded stayed in its first-load state
70
+ permanently: the spinner ran at full rate for the life of the process while the
71
+ body rendered "loading actions…" directly above the error explaining that it had
72
+ failed. Measured at 7.8% of a core and 9.8 MB/hr of terminal writes,
73
+ indefinitely, triggered by something as ordinary as closing a laptop lid.
74
+ Motion now means "still working"; the error line means "not working".
75
+ - **`Enter` is guarded against key repeat.** Holding it down spawned a
76
+ `gh <kind> view --web` per repeat event -- roughly thirty a second -- each
77
+ opening another browser tab. The `r` key beside it had this guard all along.
78
+ - **The status bar adapts to narrow panes.** It was the only band that did not:
79
+ the table swaps to a compact header, the tab bar to short labels, the panel
80
+ edges drop their labels, and the status bar just let ink truncate. Because each
81
+ hint truncates individually the loss was silent -- at 45 columns the bar read
82
+ `Move: | Open: | Refresh |Quit:…`, dropping the arrows, `Refresh`'s key, and
83
+ most of `Quit`, which is the one hint someone stuck in a full-screen app needs.
84
+ It now falls back to `↑↓ Ent r q`, which keeps every key.
85
+ - **`--doctor` no longer prints the value of environment variables it was never
86
+ told about.** It discovers every `GH_*`/`GITHUB_*` variable that is set, and
87
+ printed the value of any whose *name* did not end in `_TOKEN`/`_SECRET`/
88
+ `_PASSWORD`/`_KEY` -- so `GH_APP_PEM` (an entire RSA private key) and
89
+ `GITHUB_OAUTH` were reproduced in full, in the one report that says it is safe
90
+ to paste into a bug report. Printing a value is now opt-in and the list is
91
+ curated; anything discovered reports `set` or `not set`.
92
+ - **`--doctor --verbose` produces the log it advertises.** argv was applied to
93
+ runtime state in two places and they had drifted, so the doctor path silently
94
+ dropped `--verbose`, `--refresh` and `--tab`. There is one application site now.
95
+ - **The 60s cursor clear no longer costs you your place.** Clearing the selection
96
+ left the scroll offset behind, and the next arrow key seeded from the top of the
97
+ list -- so scrolling to row 80 of 150, reading for a minute and pressing down
98
+ put you back at row 1, with no scroll animation to notice. Movement now seeds
99
+ from what is on screen.
100
+ - **`safe()` sanitizes non-string input.** It returned early for anything that
101
+ was not a string, skipping both the control-character strip and the length
102
+ clamp -- the guarantee was being provided by GitHub's schema rather than by the
103
+ function that claims to provide it.
104
+ - **The `!` "CI is red" marker stays in the tab bar**, where a label anchors it,
105
+ instead of also being interpolated into the frame's bottom edge as `4 of 4!`.
106
+ - **A blind Security tab no longer reports a confident zero.** When all three
107
+ alert endpoints fail -- an expired SAML session, a token without
108
+ `security_events`, an org OAuth restriction -- the tab rendered
109
+ `4:Security (0)`, byte-identical to a genuinely clean repository, on the one
110
+ surface where a false all-clear is the worst available answer. It now reads
111
+ `(?)`. A repository that simply has Advanced Security switched off is a
112
+ different thing and stays quiet, because that is an answer rather than an
113
+ absence of one.
114
+ - **A failing tab backs off instead of re-spawning `gh` every tick.** The three
115
+ list tabs had no backoff at all, so a wedged tab launched a subprocess every
116
+ five seconds indefinitely -- 720 an hour against a token already refusing --
117
+ and rate limiting was deliberately excluded from the ladder that did exist,
118
+ which had it backwards: GitHub's secondary limiter keys on sustained rate
119
+ against a limited *token*, so hammering can turn a self-clearing limit into a
120
+ longer block that also hits `git push`. Measured: two `gh run list` calls in
121
+ forty seconds where there were eight. The `r` key bypasses and clears the
122
+ backoff, because a refresh that silently declines to refresh would be worse
123
+ than no key at all.
124
+ - **Errors on the Actions, Issues and PR tabs say what to do.** They rendered raw
125
+ `gh` stderr while the Security tab one across had been classifying the same
126
+ failures into remedies all along. An expired token now reads "GitHub
127
+ authorization failed -- try `gh auth login` or `gh auth refresh`". Unclassified
128
+ failures still show their real message: inventing a remedy for something
129
+ nobody recognised would be worse than showing what happened.
130
+ - **Bidi override characters are stripped from remote text.** `U+202A`-`U+202E`
131
+ and the isolates measure as zero columns, so they cost no width and survived
132
+ truncation -- one `RLO` in an issue title makes the rest of that cell render
133
+ reversed on any terminal with bidi reordering, which is the same "the row does
134
+ not show its data" failure the control-character strip already prevents.
135
+ Deleted rather than replaced with a space, because they measure zero and a
136
+ space would shift every cell to its right. Genuine RTL, `LRM`, emoji, ZWJ
137
+ sequences and CJK are untouched.
138
+ - **`REPO_PATTERN` rejects `owner/..`.** It reached the API path, and `gh`
139
+ forwards the dot segment unnormalized, so GitHub resolved it to a different
140
+ endpoint than the one intended. Names that merely contain or start with a dot
141
+ -- `owner/.github` -- are still valid.
142
+ - **Backoff deadlines use a monotonic clock.** They measure elapsed time, and a
143
+ laptop resume or an NTP step could hold an alert source in backoff for an hour
144
+ of apparent time that never passed. Staleness deliberately stays on wall-clock
145
+ time, because a sleep gap is exactly what it exists to report.
146
+ - **A crash no longer orphans `gh` children.** The handlers exited without
147
+ unmounting, so the cleanup that aborts in-flight subprocesses never ran. Both
148
+ the crash output and the `--verbose` log are now redacted, like `--doctor`
149
+ already was -- all three are artifacts users are told to attach to bug reports,
150
+ and `gh` error messages quote the URL they failed on.
151
+
152
+ ### Changed
153
+
154
+ - **The spinner runs at 200ms rather than 100ms.** It is the single largest CPU
155
+ term in the app, because every frame makes ink rebuild and diff the whole
156
+ output string: measured 7.8% of a core at 100ms against 3.9% at 200ms, on a
157
+ 0.33% idle floor. The motion is load-bearing -- it is the only thing separating
158
+ an executing run from a queued one -- so it is slowed, never stopped.
159
+ - **Only an executing Actions row receives the spinner frame.** It went to every
160
+ visible row, so a prop changing several times a second defeated row
161
+ memoisation for all of them: 7,960 row renders over 20 seconds against 238.
162
+ - **The panel names the repository** when it was chosen explicitly via `--repo`
163
+ or `GH_REPO` -- `╭─ Actions · acme/widget ─`. With several panes open, or after
164
+ changing directory, nothing on screen said which repository a pane was watching.
165
+ It is dropped before the tab name when the pane is too narrow for both.
166
+ - **Column density is decided per tab.** One global breakpoint meant the widest
167
+ tab decided for the narrowest: Pull requests needs 61 columns and Security only
168
+ 44, so Security dropped two columns a full 17 columns before it had to. Only
169
+ one tab is on screen at a time, so there is nothing to be inconsistent with.
170
+ - **Below 24 columns the pane says "too narrow"** instead of rendering a table
171
+ that cannot fit. Under that width even the compact columns overflow, which
172
+ hard-wraps every row and drives ink into repainting the whole screen each
173
+ frame -- reachable just by dragging a sidebar narrow. Widening recovers
174
+ immediately.
175
+ - **The Security tab collapses its not-enabled notes into one line.** Each
176
+ unavailable alert source took a full row above the column header, so any
177
+ repository without Advanced Security permanently spent two rows -- about a
178
+ tenth of a twenty-row pane -- restating a fact that will never change, on the
179
+ tab meant to make real alerts stand out. Failures that are not "not enabled"
180
+ keep their own lines, because those are actionable and transient.
181
+ - **Alert ordering is pinned rather than left to each endpoint's default.**
182
+ Severity ranking runs over one page, so the page boundary decides what can be
183
+ ranked at all -- a critical alert sitting past it was never fetched, and the
184
+ pane showed "100+" with a screen of moderates. Newest-first at least makes the
185
+ cut deterministic.
186
+ - **`-v` is no longer an alias for `--version`.** This CLI also has `--verbose`,
187
+ so `gh-glance -v 2>log` -- what you type when you want the log -- printed a
188
+ version string and exited 0. The argv surface exists to make typos fail loudly,
189
+ and this was the one flag that failed quietly. `--version` is unaffected.
190
+
191
+ ### Documentation
192
+
193
+ - **Corrected a claim this project made about itself in 0.4.0 and repeated in the
194
+ 0.4.1 release notes.** The status bar's `↑↓` glyphs were described as "a
195
+ deliberate, tested exception" whose double-width rendering "fails loudly" under
196
+ `npm run test:pty`. No such assertion exists or could: each hint truncates
197
+ individually, so the failure mode is silent text loss rather than overflow, and
198
+ at 80 columns the panel border is 79 cells against a 54-cell status bar, so the
199
+ bar never sets the maximum a width check would measure. The glyphs are fine;
200
+ the guarantee was not real, and a comment asserting coverage that does not
201
+ exist is worse than an acknowledged gap.
202
+ - The README no longer claims every run state has a distinct glyph under
203
+ `NO_COLOR`. Timed-out, action-required and running share one, as do skipped,
204
+ neutral, stale and queued. The tab bar's `!` marker is what actually answers
205
+ "is CI red" without colour, and it does.
206
+ - `eslint.config.js` no longer describes an inline `exhaustive-deps` suppression
207
+ that has never existed; the rule is now an error, which it can be precisely
208
+ because there is nothing to suppress.
209
+
8
210
  ## [0.4.1] - 2026-08-04
9
211
 
10
212
  Two fixes to the interactive surface: a selection marker that outlived its
@@ -329,6 +531,8 @@ engineering, security, QA and UX. What follows is what changed as a result.
329
531
  - The `main` field from `package.json`. It advertised the file as importable,
330
532
  but importing it took over the terminal or exited the host process.
331
533
 
534
+ [0.5.1]: https://github.com/juan294/gh-glance/releases/tag/v0.5.1
535
+ [0.5.0]: https://github.com/juan294/gh-glance/releases/tag/v0.5.0
332
536
  [0.4.1]: https://github.com/juan294/gh-glance/releases/tag/v0.4.1
333
537
  [0.4.0]: https://github.com/juan294/gh-glance/releases/tag/v0.4.0
334
538
  [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