Skip to content

feat(cli): presentation and experience polish across every human-facing surface#315

Merged
upsetbit merged 8 commits into
masterfrom
feat/cli-presentation-polish
Jul 2, 2026
Merged

feat(cli): presentation and experience polish across every human-facing surface#315
upsetbit merged 8 commits into
masterfrom
feat/cli-presentation-polish

Conversation

@upsetbit

@upsetbit upsetbit commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

CLI presentation and experience pass: every human-facing surface was run in a real pseudo-TTY against the live local store, defects were fixed, and the loop repeated until the output read cleanly at 60, 80, 100, and 140 columns. Plain (pipe) and --json shapes are untouched except where noted.

What changed

Timeline

  • Metadata columns now size themselves to the widest value in the rendered set (capped: device 18, agent 12, project 32) instead of fixed 7–14-char buckets. mz-codes/mz… on every row becomes mz-codes/mz-operator-1.
  • Narrow terminals shrink device before project (contract compression order); overflowing project labels drop the owner segment first, then truncate from the left so the distinguishing tail survives.
  • Prompts absorb the leftover row width up to 96 columns (was a flat 60-rune cap).
  • Cardinality-1 suppression no longer loses information: when every row shares one project/device, the context line names it (prosa · local · all projects · last 1d · project mz-codes/mz-operator-1 · device tbox).
  • Scope labels are normalized: scoped to git@github.com:c3-oss/prosa.gitscoped to c3-oss/prosa; paths under $HOME render as ~/….
  • Device labels trim FQDN hostnames to the first dot segment (ip-192-168-0-16.ec2.internalip-192-168-0-16).

Search

  • Snippet truncation kept dropping the FTS markers, so most hits rendered with no highlight at all. Truncation now walks the marked runes, keeps the first match visible with leading context, and re-emits «» pairs around whatever survives the cut.
  • The empty-state hint drops the --all suggestion when --all is already set.

Analytics + tables

  • Numeric columns right-aligned and thousands-separated; PROJECT collapses remotes to owner/repo and $HOME paths to ~; STARTED renders as local wall-clock; cost reads $2,192.29 instead of $2192.2861. Plain mode keeps raw values for scripts.
  • New shared render.Table primitive aligns by display width. This fixes a real bug: profiles list and devices list used text/tabwriter, which counts ANSI escapes as text — styled headers misaligned every column.

Show

  • Header resolves the device friendly name instead of printing fingerprint hex; a tokens row joins the metadata block when usage was measured.

Sync (found live during the run)

  • A prosa sync against an unreachable server had been running for 12+ hours: the HTTP client had no timeouts and the push circuit breaker only recognized CodeUnavailable, so every imported session paid a full kernel TCP retry cycle. The transport now bounds connection phases (5s dial / 5s TLS / 60s response header — body transfer stays unbounded for large pushes), and isRemoteUnavailable also trips on net errors and DeadlineExceeded. Re-run after the fix: full import of a 5.6k-session backlog completed in ~2 minutes with the documented Remote: server unavailable … summary line.
  • Summary rows for Denoise/Remote/Warnings align with the count rows.

Help + errors

  • Root help groups commands (Getting started / Explore your history / Import & sync / Remote server) and gains a short description of what bare prosa does.
  • --last bogus reads as one sentence instead of repeating the wrapped Go error.

Docs

  • docs/cli/rendering-contract.md updated for all of the above; screens.md, cli/README.md, usage.md scope-label examples now match the normalized form; architecture/cli.md lists the new table.go.

Validation

  • go test ./... and go test -race ./internal/cli/... green; new unit tests for column sizing, label fitting, uniform-value context segments, marked-snippet truncation, table alignment, cost/timestamp/project display, and the unreachable-server classifier.
  • golangci-lint run ./internal/cli/... — 0 issues; gofumpt clean on touched files; just lint-md clean.
  • prosa-cli-ux-reviewer and prosa-docs-reviewer agents ran over the diff; docs drift they flagged is fixed in the docs commit.
  • Every surface exercised in a pty at multiple widths against the real store; plain/pipe output verified ANSI-free with stable columns.

🤖 Generated with Claude Code

upsetbit and others added 8 commits July 2, 2026 00:33
… visible

- metadata columns now take their widest value (capped: device 18, agent 12, project 32)
- narrow terminals shrink device before project, following the contract compression order
- overflowing project labels drop the owner segment first, then truncate from the left
- prompts absorb the remaining row width up to 96 columns (was a flat 60-rune cap)
- cardinality-1 suppression now surfaces the uniform project/device in the context line
- scope labels normalize git remotes to owner/repo and abbreviate $HOME paths to ~
- device labels trim FQDN hostnames to their first dot segment, like hostname -s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Long snippets used to lose their FTS match markers whenever the text
exceeded the row width, so most hits rendered without any highlight.
Truncation now walks the marked runes, shifts the window so the first
match stays visible with a third of leading context, and re-emits the
marker pairs around whatever survives the cut.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new render.Table primitive aligns by display width, so styled cells
  no longer shift columns (tabwriter counted ANSI escapes as text and
  misaligned the profiles and devices headers)
- numeric columns are right-aligned and thousands-separated in TTY
- PROJECT collapses git remotes to owner/repo and $HOME paths to ~
- STARTED and LAST SYNC render as local wall-clock time in TTY
- estimated cost reads as dollars: $2,192.29 instead of $2192.2861
- plain mode keeps raw tab-separated values for scripts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dead server address used to hang sync for hours: the HTTP client had
no timeouts, and the push circuit breaker only recognized the explicit
Unavailable code, so every imported session paid a full kernel TCP
retry cycle. Observed live as a sync stuck for 12h on a Tailscale IP.

- transport now bounds connection phases (5s dial, 5s TLS handshake,
  60s response header); body transfer stays unbounded so large raw
  pushes on slow links are never cut off mid-stream
- isRemoteUnavailable also trips on net errors and DeadlineExceeded,
  so the first failed connection short-circuits the remaining pushes
- summary rows for Denoise/Remote/Warnings use the same padded muted
  label as the count rows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The show header printed the raw device fingerprint hex; it now resolves
the friendly name from the local store. A tokens row joins the metadata
block when the session has measured usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- root help lists commands under Getting started / Explore your
  history / Import & sync / Remote server groups, with a short Long
  description of what bare prosa does
- window parse errors read as one sentence instead of repeating the
  wrapped Go error
- the empty-search hint drops the --all suggestion when --all is
  already set

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- contract: content-sized timeline columns, uniform-value context-line
  segments, normalized scope labels, snippet truncation that keeps the
  highlight, analytics humanizations, command tables section, sync
  fail-fast note, conditional empty-search hint
- screens/README/usage: scope label examples show owner/repo form
- screens: narrow-terminal example matches the in-place column shrink
- architecture/cli: render package list gains table.go

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
%q escaped embedded quotes and backslashes, adding visual noise and
pushing rows past the width the truncation had just computed. Prompts
and search session lines now render inside literal quotes; display
text is sanitized to printable runes so nothing needs escaping.

Also from review: the $HOME abbreviation is one shared helper
(render.AbbreviateHome), and isRemoteUnavailable drops a dead
*net.OpError check already covered by the net.Error match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@upsetbit upsetbit merged commit edfd2b1 into master Jul 2, 2026
4 checks passed
@upsetbit upsetbit deleted the feat/cli-presentation-polish branch July 2, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant