Width-aware rendering: the banner now fits any terminal#3
Conversation
The banner previously rendered at full FIGlet width and let the terminal wrap it, which turned narrow windows (tmux panes, phone SSH sessions) into garbled block characters. Sysgreet now detects the terminal width and walks a fallback ladder before printing: full hostname in the configured font, hostname without its domain (full name moves to an info line), progressively narrower fonts ending with the newly embedded Small font, and finally a single ruled header line that fits any width. Body lines that still overflow are clipped with an ellipsis. Color handling moves into a new internal/terminal package that owns the ANSI palette (previously duplicated and drifted between ascii and render) and detects capability once at startup. This fixes NO_COLOR only applying to resource highlights while the banner gradient kept its escapes, stops ANSI codes leaking into piped output, honors TERM=dumb, and enables virtual terminal processing on Windows so legacy consoles get plain text instead of raw escapes. Truecolor terminals now get a smooth 24-bit gradient interpolated from the configured stops. Also fixed along the way: --text and --demo previously ignored the loaded config entirely; compact mode embedded the multi-line art in its "single line" output; and a typo in ascii.font silently picked a random font every login instead of falling back to the default. New: --json for scripting (structured sections with raw percentages, never prompts), --font, --width, --no-color, --config, --list-fonts, and layout.max_width / SYSGREET_LAYOUT_MAX_WIDTH to cap the width.
Collectors previously ran one after another, so on Windows the 100 ms CPU sample alone blew the 50 ms startup budget, and any single slow metric source could hang the login shell indefinitely. Each collector now gathers in its own goroutine (they write disjoint Snapshot fields) and shares a 250 ms deadline; a collector that misses it just drops its section, which the banner already degrades around.
The lint job pinned golangci-lint-action@v6 with version: latest, which now resolves to golangci-lint v2 and rejects the v1-format .golangci.yml outright. Migrated the config with `golangci-lint migrate` and pinned the action (v8) and linter (v2.5.0) so lint results stop depending on whatever "latest" means this week.
README highlights the width degradation ladder and script-friendly output, documents the new flags (--json, --font, --width, --no-color, --config, --list-fonts) and layout.max_width. CHANGELOG gains a v1.1.0 entry. CONTRIBUTING notes the new internal/terminal package plus the output rules (width and color must flow through it), and the examples show narrow-terminal degradation and jq-based scripting.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 174b7ef797
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Four follow-ups to the width-aware rendering work: - Compact mode output is now clipped to the terminal width like every other line; it previously returned before the clipping path ran. - Domain shortening (pve1.home.lan -> PVE1) is now opt-in via RenderOptions.ShortenDomain and only the hostname header sets it, so --text "v2.1.0" can shrink or clip but never silently loses everything after the first dot. - layout.max_width now caps the whole banner, not just the header art: the config folds into the terminal env before the layout renderer is built (render.ApplyConfig), with --width still winning over both. - ascii.monochrome once again suppresses the resource threshold highlights; keying the colorizer off the terminal profile alone had regressed that.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a74b74264
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Gather no longer waits past its deadline: collectors send apply functions over a channel and the loop selects against ctx.Done, so a collector stuck in a blocking syscall (ignoring cancellation) is abandoned at 250 ms with the finished collectors' results kept. The snapshot is only touched from the calling goroutine, so stragglers cannot race the caller. - env.Width is now the single width authority: the banner header uses it directly instead of re-applying layout.max_width, so --width 100 with max_width 40 renders one consistent width instead of a 40-column header over 100-column body lines. - Section titles are clipped like body lines; --width 8 no longer leaks a 9-column "Resources" heading. - An explicit config path (--config / SYSGREET_CONFIG) is exclusive: when the file is missing, sysgreet uses built-in defaults instead of silently falling back to a different config file on the host.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e621775bb7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The width ladder's hostname fallback line was leaking into the JSON header: at narrow widths buildHeader shortens the art and prepends the full hostname to Header.Lines, so header[0] flipped between the OS line and the hostname depending on terminal size. JSON mode now builds against a neutral environment, so scripted output is stable regardless of where it runs.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76f5814302
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
When the width ladder shortens a dotted hostname, buildHeader adds the full name as an info line for the art's benefit. Compact mode already leads with the full hostname, so that line rendered it twice (PVE1.HOME.LAN | pve1.home.lan | ...). Compact output now skips it.
Two edges in the width guarantee: a timed-out system collector left a blank OS header line (and an empty JSON header element), and widths of one or two columns overflowed because the fixed two-space body indent was prepended after clipping. Blank OS lines are now omitted and the indent is dropped when the width cap cannot fit it.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a4a910e01
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two more review findings. Clipping counted runes, so double-width characters (a CJK username or home path) could pass the check while overflowing the terminal on screen; the terminal package now owns RuneWidth/DisplayWidth/Clip covering the East Asian wide and emoji ranges, and the render and ascii layers measure with them. And when the system collector misses the gather deadline, the banner rendered Time: Mon, 01 Jan 0001 00:00:00 UTC and User: unknown from the zero-valued snapshot; those lines are now omitted when the data is absent, matching how every other missing metric degrades.
|
@codex review |
go-figure's strict mode kills the process with log.Fatal on non-ASCII input, so --text "日本語" died before printing anything; non-strict mode instead drops uncovered characters, which silently mangles mixed text like web-サーバ. Rendering is now non-strict as a backstop, and any text outside printable ASCII skips the FIGlet ladder entirely in favor of the plain ruled header, which preserves it verbatim and clips by display columns.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f830a532da
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The plain-header fallback capped its rule at 60 columns even when the terminal was wider, clipping a 70-column name that a 100-column pane could show in full; 60 is now just the preferred rule length and long names widen the rule up to the available width instead of losing characters. And bootstrapping --config /path/config.toml wrote YAML into the .toml file, which the loader then failed to parse; the default config is now marshaled in the format the target extension implies.
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The banner has always rendered at full FIGlet width and let the terminal wrap it, which turns narrow windows — split tmux panes, phone SSH sessions — into garbled block characters. This PR makes sysgreet measure the terminal before printing and degrade gracefully instead.
Width ladder
When the art would overflow, sysgreet steps down in order:
pve1.home.lan→PVE1; the full name moves to an info line)standard, then the newly embeddedSmall)═════ PVE1 ═════— that fits any widthBody lines that would still overflow are clipped with an ellipsis. Width comes from the TTY, falling back to
COLUMNS, and can be capped withlayout.max_widthor previewed with--width.Color handling fixes
New
internal/terminalpackage owns the ANSI palette (previously duplicated and drifted betweenasciiandrender) and detects capability once at startup:NO_COLORnow covers the whole banner — the hostname gradient used to keep its escapesTERM=dumbhonored←[94mCOLORTERM=truecolor) get a smooth 24-bit gradient interpolated from the configured stopsOther fixes
--textand--demonow honor the loaded config (both silently ignored it)SYSGREET_DEBUG) instead of picking a random font every loginversion: latesthad started resolving to v2, which rejects the old config outright, so lint was silently broken.gitignore'ssysgreetentry anchored to the repo root — it was ignoring new files undercmd/sysgreet/New surface
--json(structured output for scripting, never prompts),--font,--width,--no-color,--config,--list-fontslayout.max_width/SYSGREET_LAYOUT_MAX_WIDTHValidation
golangci-lintclean; cross-compiles for windows/amd64 and darwin/arm64golang.org/x/termpinned at v0.20.0 to keep the Go 1.22 floorReview follow-up in
4a74b74: compact-mode output is clipped to width,--textcontent is never rewritten at dots (domain shortening is hostname-only now),layout.max_widthcaps the whole banner rather than just the header, andascii.monochromesuppresses threshold highlights again.