Skip to content

feat: horizontal scrolling for the stats table (#14)#143

Merged
lesovsky merged 22 commits into
masterfrom
feature/horizontal-scroll
Jun 23, 2026
Merged

feat: horizontal scrolling for the stats table (#14)#143
lesovsky merged 22 commits into
masterfrom
feature/horizontal-scroll

Conversation

@lesovsky

Copy link
Copy Markdown
Owner

Summary

Adds by-column horizontal scrolling to the main stats table in pgcenter top, with a frozen first column — long-requested support for narrow terminals (addresses #14, open since 2015).

On narrow screens the stats table no longer silently cuts off-screen columns (e.g. query on the activity screen). Users can now scroll the table sideways while keeping the row identifier (PID / datname / table name) in view.

What's included

  • [ / ] scroll the column window left / right (one column per press). Shift/Ctrl+arrow are not distinguishable by termbox, so printable keys were chosen.
  • First column frozen — always visible, name shown bold.
  • Edge markers / on the header row indicate hidden columns to each side; shown only when there is something to scroll to.
  • Partially-visible last column — a wide trailing column (like query) is shown truncated at the edge, as before, rather than disappearing.
  • Scroll offset is reset on every view switch (both switch paths) and persists across auto-refresh ticks within a screen.
  • Scroll and sort (/) are orthogonal.

Scope is confined to the main dbstat table: extra side panels, the record/report pipeline, and the existing pg_stat_io / pg_stat_statements sub-screen splits are intentionally untouched.

Process & quality

Built via the full SDLC pipeline (user-spec → tech-spec → task decomposition → waved implementation), each stage multi-validated. Per-task code/test review (2–3 rounds where needed). Manual QA in a narrow terminal surfaced two bugs (wide last column disappearing; redundant edge markers) — root-caused to the column-window fit semantics, fixed with property-test coverage proving the last column is always reachable.

  • Unit + render-level tests for the window logic, markers, alignment invariant, scroll handlers, offset reset.
  • make build, make lint green. (make vuln flags a pre-existing stdlib advisory unrelated to this change.)

Full spec/decision trail under docs/features/009-feat-horizontal-scroll/.

🤖 Generated with Claude Code

Alexey Lesovsky and others added 22 commits June 23, 2026 09:02
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce criteria

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndex, ColsWidth bounds, S-key, reviewers rationale

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mp), remove junk lines, fix context paths

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Branch set to feature/horizontal-scroll for implementation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cution plan

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ment

- Reserve edge-marker (‹/›) width in visibleColumns scroll budget and mirror
  it as blank fillers in data rows, so the visible width of the header row
  equals every data row (alignment invariant). visibleColumns now returns a
  columnWindow struct computed once in renderDbstat and passed to the header
  and data printers (removes the triple call / desync risk).
- Conservative marker reservation avoids the budget<->window cycle: left marker
  reserved when clamped offset > 0, right marker when a full-budget probe already
  shows columns hidden right (shrinking budget can only keep them hidden).
- Tests: mid-offset asserts BOTH markers; new alignment-invariant litmus
  (ANSI-stripped header width == data width); frozen-bold escape pinned;
  right marker position pinned to line end; truncation branch covered.
  Test_visibleColumns updated for the marker-reserved budget.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ker (issue #14 QA)

The visibleColumns window included a scrollable column only if it fit ENTIRELY
into the remaining width budget. The activity/statements "query" column is aligned
by content and is almost never narrow enough to fit in full, so it was dropped from
the window (QA bug 2: query not shown) and, because the last column then fell short
of ncols-1, the right edge marker was drawn even on wide screens where everything
"essentially" fits (QA bug 1: spurious ›).

Change countFit to count a column as visible when its START position is within the
budget (partial visibility of the trailing column, truncated by gocui at the screen
edge — the pre-scroll behaviour). The same start-in-budget rule applies to the
backward walk that computes maxOffset, so maxOffset shrinks consistently and the
"last column visible at max offset / nothing hidden right" invariant still holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… always reachable

The backward-walk computing maxOffset measured trailing columns against the
full baseBudget, but the forward-walk that builds the window at any non-zero
offset measures against baseBudget - markerWidth (the left marker ‹ is always
present when clamped > 0). The mismatch let the backward-walk overestimate how
many trailing columns fit, so at the maximum clamped offset the forward-walk
fell short of the last column: last < ncols-1 and hiddenRight stayed true
forever, making the last column unreachable by scrolling.

Reserve the guaranteed left marker in the backward-walk whenever scrolling is
actually needed (maxOffset > 0), guarding against a non-positive tail budget.
Shrinking the budget can only push maxOffset up and keep it > 0, so the
left-marker assumption stays consistent in one extra pass.

Add property test Test_visibleColumns_maxOffsetReachesLastColumn over
ncols 2..8 x width set x termWidth 40..120: at max offset the last column is
always visible and no right marker remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fset marker reserve)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesovsky lesovsky merged commit 9bbd158 into master Jun 23, 2026
1 check passed
@lesovsky lesovsky deleted the feature/horizontal-scroll branch June 23, 2026 08:09
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