Skip to content

cli: terminal UI polish (capability detection, grouped help, error blocks, progress bar) - #11

Merged
DibbayajyotiRoy merged 2 commits into
mainfrom
feat/tui-polish
Oct 27, 2026
Merged

DibbayajyotiRoy merged 2 commits into
mainfrom
feat/tui-polish

Conversation

@DibbayajyotiRoy

Copy link
Copy Markdown
Collaborator

What

A pass over everything a person sees at a terminal, built on the internal/ui base from #4. Nothing a script or agent reads changes: piped, redirected, in CI, under NO_COLOR / CLICOLOR=0 / TERM=dumb, and with --json or -q, the output is byte-for-byte what it was.

  • Capability detection (ui.Caps): 16 / 256 / 24-bit colour, UTF-8 locale, OSC 8 hyperlinks, light background (COLORFGBG). Read from TERM, COLORTERM, TERM_PROGRAM, WT_SESSION, VTE_VERSION, LC_ALL/LC_CTYPE/LANG, FORCE_HYPERLINK. The zero Caps is the most conservative terminal; ui.Force keeps the old 16-colour UTF-8 profile, so existing tests and escape codes are unchanged.
  • Design tokens: colours are roles (Accent, Success, Warn, Danger, Muted, Strong), not names. Signals stay in the terminal's own palette, so Solarized / Dracula / light themes tune them. Only the accent (teal) and the amber leave it, to colours with contrast on black and on white (the accent is 3.7:1 on white, 5.7:1 on black).
  • Glyph sets with an ASCII fallback outside a UTF-8 locale and on the kernel console: ✓ ✗ ! › · … become + x ! > - ..., the spinner becomes | / - \.
  • Width-aware text: Width, Truncate, Wrap, StripANSI count terminal cells (CJK and emoji are two, combining marks and escapes are none). Wrap never splits a URL or id and keeps a line's indentation.
  • Error block: the ERR:: code is muted so the sentence leads, the hint row is the accent, keys align, long text wraps under its own column (not under the key), URLs are links where the terminal has them.
  • Spinner: measured against the terminal on every frame, so it follows a resize and never wraps. New Spinner.Progress(done, total) draws a determinate bar with count and ETA (⠹ Batch job ━━━━━━━━╸───────── 12/24 · 4.4s · ETA 4s), dropping the ETA, then the bar, then the label's tail as the line narrows. The ETA waits for 2 s and one finished item. Nothing calls it yet (see follow-ups).
  • --help on a live terminal: commands grouped by purpose, coloured headings and names, defaults and (GET /v1/...) routes muted, descriptions wrapped under their own column, an examples section (root examples are checked by a test to be real commands), docs link. Installed with SetHelpFunc from a new help.go init(), so no command file and not root.go changed. Anything not live (and --json / -q) gets cobra's help.
  • view.go: rows wrap and link on a terminal, marks use the glyph set (warn is amber, not just bold), tables measure cells. Signatures unchanged.

No new dependencies (golang.org/x/term and pflag were already direct).

Before and after (pty, 100 columns)

scrape https://example.com/blocked

before
{red}✗{/} {bold}ERR::TARGET::BLOCKED: the site refused the request after 3 attempts through the residential pool; it answered 403 with a bot-detection challenge page{/}
  {dim}hint     {/}  retry with --render to run the page's JavaScript, or add --country to change the exit location
  {dim}target   {/}  returned 403
  ...

after
{red}✗{/} {dim}ERR::TARGET::BLOCKED:{/} {bold}the site refused the request after 3 attempts through the residential pool;{/}
  {bold}it answered 403 with a bot-detection challenge page{/}
  {accent}hint     {/}  retry with --render to run the page's JavaScript, or add --country to change the exit
             location
  {dim}target   {/}  returned 403
  ...

spicrawl --help (before: alphabetical, one flat list, a flag default wrapped mid-URL by the terminal)

{bold}EXTRACT DATA{/}
  {accent}scrape{/}      Fetch one page (or many from stdin) as markdown, html, text, pdf or extracted data
  {accent}batch{/}       Run many URLs as one asynchronous job {dim}(POST /v1/batch){/}
  {accent}sessions{/}    Persisted browser sessions (cookies and storage reused across scrapes)
  {accent}browser{/}     Cloud browsers over a DevTools Protocol (CDP) WebSocket
{bold}SET UP{/}  login logout auth init mcp skill config
{bold}MONITOR{/}  status logs usage
{bold}REFERENCE{/}  docs schema exit-codes version completion help
{bold}EXAMPLES{/}  ...
{bold}FLAGS{/}
      {accent}--base-url{/} {dim}string{/}   API base URL (default: $SPICRAWL_BASE_URL, then the config file, then
                          https://api.spicrawl.com)

LC_ALL=C: {green}+{/} example.com {dim}200 - 1 credit - 2.5s{/} and a | / - \ spinner.

Verified

  • go vet ./..., gofmt -l, go test -race ./... pass. The golden suite (all cases, 0 fail) passes with testdata/ untouched.
  • Piped --help is identical to main for all 51 command paths, and unchanged under a TTY with NO_COLOR, CI, TERM=dumb, CLICOLOR=0 and --json / -q (a test locks the last two).
  • GOOS/GOARCH builds: linux, darwin, windows x amd64, arm64; go vet for windows and darwin.
  • Unit tests: capability detection per terminal (colour, locale, hyperlinks, light background), role resolution per colour level, ASCII fallback, Width/Truncate/Wrap, bar and ETA, progressLine fits every width from 12 to 200 columns, a mid-run resize, error-block wrapping and links, help layout (every command listed once in one name column, sections in order, no internal engine names on any command's live help, root examples parse).
  • Checked in real terminals (vhs/xterm.js, Dracula and GitHub Light themes, 256-colour and truecolor).

Known limits

  • Resize: the line is re-measured every 80 ms frame, so it is correct one frame after a resize. A terminal that re-wraps old output on shrink can leave one stale row; clearing it would mean moving the cursor up, which erases a real line on terminals that do not reflow, so it is not done.
  • Wide-character tables cover CJK, Hangul, fullwidth forms, emoji and common combining marks, not all of UAX cli: terminal UI polish (capability detection, grouped help, error blocks, progress bar) #11.
  • Legacy Windows console stays plain (as before); enabling VT processing there is a separate change.

Follow-ups that need files outside this PR

  • root.go (problemRows): add {Key: "docs", Val: prob.DocURL} (the API sends doc_url); the error block already links it. Also the request row says the id twice (01J9... (spicrawl logs get 01J9...)); spicrawl logs get 01J9... alone is enough.
  • batch_wait.go: call spin.Progress(done, total) so batch wait / submit --wait show the bar and ETA. Keep the label to the job id, since the count moves to the bar.
  • Hard-coded glyphs that bypass the ASCII fallback: status.go (✗/✓ at the summary lines), auth.go (✓/✗ verdicts), sessions.go (✓ Session created), agents.go (✓/– cells and the · join). Use viewMark(u, markOK) / u.Glyphs().
  • logs.go, sessions.go: spinner labels with a literal · should use u.Glyphs().Dot.
  • auth.go: u.Cyan(authKeysURL) can be u.Link(authKeysURL, "").
  • ui.Cyan stays as an alias of Accent so nothing in flight breaks; new code should use the role names.

…ocks, progress bar)

Everything here is for a person at a terminal. Piped, redirected, in CI, under
NO_COLOR/CLICOLOR=0/TERM=dumb, and with --json or -q, stdout and stderr are
byte-for-byte what they were: the golden tests pass unchanged, and 51 piped
--help screens are identical to main's.

internal/ui
- Caps/Detect: 16 / 256 / 24-bit colour, UTF-8 locale, OSC 8 hyperlinks and a
  light background, read from the environment. Force keeps the old 16-colour
  UTF-8 profile, so existing tests and escape codes are unchanged.
- Semantic roles (Accent, Success, Warn, Danger, Muted, Strong) instead of
  colour names. The signals stay in the terminal's own palette so a theme tunes
  them; only the accent (teal) and the amber leave it, to colours with contrast
  on black and on white.
- Glyph sets with an ASCII fallback (+ x ! > ... and a | / - \ spinner).
- Width, Truncate, Wrap and StripANSI measure terminal cells (wide characters,
  combining marks, escapes). Wrap keeps a long URL whole and keeps indentation.
- Error block: the ERR:: code is muted so the sentence leads, the hint is the
  accent, keys align, long text wraps under its own column, URLs are linked.
- The spinner measures the terminal every frame, so it follows a resize. New
  Spinner.Progress(done, total) draws a determinate bar with a count and an
  ETA, dropping the ETA, then the bar, then the label's tail as the line
  narrows.
- Elapsed formats hours (1h05m).

internal/cmd
- help.go: spicrawl --help and <cmd> --help on a live terminal get grouped
  commands, coloured headings and names, muted defaults and API routes,
  wrapped descriptions and the docs link. Installed with SetHelpFunc from
  init(), so no command file changes. Anything not live gets cobra's help.
- view.go: rows wrap under their value column and link URLs on a terminal,
  marks use the glyph set (warn is amber), tables measure cells.
@DibbayajyotiRoy DibbayajyotiRoy self-assigned this Oct 27, 2026
@DibbayajyotiRoy DibbayajyotiRoy added bug Something isn't working enhancement New feature or request labels Oct 27, 2026
@DibbayajyotiRoy
DibbayajyotiRoy merged commit caa2003 into main Oct 27, 2026
2 checks passed
@DibbayajyotiRoy DibbayajyotiRoy mentioned this pull request Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant