Skip to content

Unlock full scrollback depth in railway ca session panes - #1065

Merged
codyde merged 1 commit into
railway/ca-manage-reset-refresh-authfrom
railway/ca-tui-deep-scrollback
Aug 11, 2026
Merged

Unlock full scrollback depth in railway ca session panes#1065
codyde merged 1 commit into
railway/ca-manage-reset-refresh-authfrom
railway/ca-tui-deep-scrollback

Conversation

@codyde

@codyde codyde commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

What

railway ca session panes could only scroll back one screenful, no matter how much history the emulator held. This removes that limit: scrollback now reaches the full retained history (4000 lines).

Why it was limited

The one-page clamp in Session::scroll_by was a deliberate workaround, documented in place: vt100 0.15.2 composes the scrolled view as scrollback[len-offset..] ++ rows[..rows_len-offset], and an offset deeper than the screen height underflows the second subtraction — a panic in debug, and in release a silent wrap that renders the live screen ("scrolling does nothing"). resize() carried a matching clamp so a shrinking pane couldn't strand an offset past the new height.

The workaround's comment said upgrading vt100 was blocked on ratatui's exact pin of unicode-width = "=0.2.0". Both halves of that blocker have since resolved upstream:

  • vt100 0.16 fixes exactly this bug (visible_rows now bounds with take(rows_len) + saturating_sub).
  • ratatui 0.30 moved to ratatui-core, which accepts unicode-width >=0.2.0, <=0.2.2, so it co-resolves with vt100 0.16.

Changes

  • Bump ratatui 0.29 → 0.30, vt100 0.15 → 0.16.
  • vt100 API move: Parser::set_size / set_scrollback are now on Screen (via screen_mut()), and Cell::contents() returns &str.
  • Remove the one-screen ceiling in scroll_by — the emulator clamps to the history that exists, and the settled offset is read back as before.
  • Remove the resize clamp; the held offset now survives reshapes (safe on 0.16) and is re-read from the emulator after set_size.

No ratatui API in this codebase changed in 0.30: the upgrade produced zero ratatui compile errors across all five TUIs (cloud agent, develop, metrics, scale, volume browser), and none of the 0.30 breaking-changes items (block::Title, Flex::SpaceAround, layout::Alignment, custom Backend impls, braille symbol tables) are used here.

Tests

Six new tests pressure the removed limit from every direction, in both debug (where the old emulator panicked) and release (where it silently no-opped):

  • scrolling_reaches_the_whole_history — 20 screens of output; scroll_by(isize::MAX) lands on the first line ever printed, isize::MIN returns to live.
  • scrolling_walks_past_one_screenful — successive wheel notches through the real scroll() entry point keep going past the pane height (the exact thing the clamp prevented).
  • a_deep_scroll_survives_resize — a deep offset rides through shrinking and growing panes without underflow or a stuck view.
  • scrollback_survives_churn — live output, scrolls, and reshapes interleaved; the held offset always matches the emulator and the view always renders.
  • scrollback_clamps_at_capacity — 4200+ lines overflow the 4000-line retention; full depth clamps to exactly 4000 and shows real (oldest retained) history.
  • a_deeply_scrolled_pane_draws_old_history — full-frame render via render_sessionscreen_linesScreen::cell with the view several screens deep: old lines drawn, tail out of view, "scrolled back" badge shown.

Full suite: 954 passed. The 3 auth_sim failures are pre-existing on master in this environment (fail identically on an unmodified checkout) and unrelated.

@codyde
codyde force-pushed the railway/ca-tui-deep-scrollback branch from e558245 to d49e7f2 Compare August 9, 2026 21:57
@codyde codyde added the release/patch Author patch release label Aug 9, 2026 — with Railway App
@codyde codyde added release/skip Author no release and removed release/patch Author patch release labels Aug 10, 2026
@codyde codyde added the release/patch Author patch release label Aug 11, 2026 — with Railway App
@codyde codyde removed the release/skip Author no release label Aug 11, 2026
Scrolling was clamped to one screenful — a documented workaround for
vt100 0.15's view composition underflowing past the screen height.
vt100 0.16 fixes that, and ratatui 0.30 (via ratatui-core) drops the
exact unicode-width pin that blocked the upgrade.

Bump both, migrate the moved Parser::set_size/set_scrollback calls to
Screen, drop the one-page ceilings in scroll_by and resize, and pin the
behavior with tests: full-depth scroll, multi-notch wheel walks, deep
offsets across resizes, churn (output + scroll + reshape interleaved),
retention-limit clamping, and a full-frame render from deep history.
@codyde
codyde force-pushed the railway/ca-tui-deep-scrollback branch from d49e7f2 to 7ee499e Compare August 11, 2026 05:29
@codyde codyde removed the release/patch Author patch release label Aug 11, 2026
@codyde codyde added the release/skip Author no release label Aug 11, 2026 — with Railway App
@codyde
codyde changed the base branch from master to railway/ca-manage-reset-refresh-auth August 11, 2026 05:29
@codyde
codyde merged commit aadd9e7 into railway/ca-manage-reset-refresh-auth Aug 11, 2026
11 checks passed
@codyde
codyde deleted the railway/ca-tui-deep-scrollback branch August 11, 2026 05:33
codyde added a commit that referenced this pull request Aug 11, 2026
Scrolling was clamped to one screenful — a documented workaround for
vt100 0.15's view composition underflowing past the screen height.
vt100 0.16 fixes that, and ratatui 0.30 (via ratatui-core) drops the
exact unicode-width pin that blocked the upgrade.

Bump both, migrate the moved Parser::set_size/set_scrollback calls to
Screen, drop the one-page ceilings in scroll_by and resize, and pin the
behavior with tests: full-depth scroll, multi-notch wheel walks, deep
offsets across resizes, churn (output + scroll + reshape interleaved),
retention-limit clamping, and a full-frame render from deep history.
codyde added a commit that referenced this pull request Aug 11, 2026
* Add railway ca manage, clear cached auth on refresh, and setup reset

`railway ca manage` jumps the TUI straight to the manage screen, skipping
the front-door menu. `--refresh-auth` now clears the local Claude
setup-token cache before re-minting, instead of only skipping the
remote reuse check (a stale/revoked cached token previously never got
replaced). `railway ca setup --reset`, and a "Reset to default" choice
in the interactive prompts, clear saved cloud agent preferences and the
cached token together.

* Unlock full scrollback depth in railway ca session panes (#1065)

Scrolling was clamped to one screenful — a documented workaround for
vt100 0.15's view composition underflowing past the screen height.
vt100 0.16 fixes that, and ratatui 0.30 (via ratatui-core) drops the
exact unicode-width pin that blocked the upgrade.

Bump both, migrate the moved Parser::set_size/set_scrollback calls to
Screen, drop the one-page ceilings in scroll_by and resize, and pin the
behavior with tests: full-depth scroll, multi-notch wheel walks, deep
offsets across resizes, churn (output + scroll + reshape interleaved),
retention-limit clamping, and a full-frame render from deep history.

* feat(ca): add Opt+[ to cycle back to the previous session (#1066)

* Unlock full scrollback depth in railway ca session panes

Scrolling was clamped to one screenful — a documented workaround for
vt100 0.15's view composition underflowing past the screen height.
vt100 0.16 fixes that, and ratatui 0.30 (via ratatui-core) drops the
exact unicode-width pin that blocked the upgrade.

Bump both, migrate the moved Parser::set_size/set_scrollback calls to
Screen, drop the one-page ceilings in scroll_by and resize, and pin the
behavior with tests: full-depth scroll, multi-notch wheel walks, deep
offsets across resizes, churn (output + scroll + reshape interleaved),
retention-limit clamping, and a full-frame render from deep history.

* feat(ca): add Opt+[ to cycle back to the previous session

Opt+] already cycles forward through open session panes; this adds the
reverse chord. Opt+[ is recognized where the terminal can express it
unambiguously: as a real ALT event under the kitty keyboard protocol
(which the TUI already enables), or as the composed curly double quote
macOS sends when Option is not mapped to Meta.

Under legacy Option-as-Meta, Opt+[ is the two bytes ESC [ — identical to
the CSI prefix every arrow key starts with — so it never surfaces as a
key event there. The chord goes dead in those terminals but can never
misfire or steal a cursor key.

Also updates the footer hint to advertise both directions.

* Lead the ca Manage tree with agents instead of projects (#1067)

* Unlock full scrollback depth in railway ca session panes

Scrolling was clamped to one screenful — a documented workaround for
vt100 0.15's view composition underflowing past the screen height.
vt100 0.16 fixes that, and ratatui 0.30 (via ratatui-core) drops the
exact unicode-width pin that blocked the upgrade.

Bump both, migrate the moved Parser::set_size/set_scrollback calls to
Screen, drop the one-page ceilings in scroll_by and resize, and pin the
behavior with tests: full-depth scroll, multi-notch wheel walks, deep
offsets across resizes, churn (output + scroll + reshape interleaved),
retention-limit clamping, and a full-frame render from deep history.

* feat(ca): add Opt+[ to cycle back to the previous session

Opt+] already cycles forward through open session panes; this adds the
reverse chord. Opt+[ is recognized where the terminal can express it
unambiguously: as a real ALT event under the kitty keyboard protocol
(which the TUI already enables), or as the composed curly double quote
macOS sends when Option is not mapped to Meta.

Under legacy Option-as-Meta, Opt+[ is the two bytes ESC [ — identical to
the CSI prefix every arrow key starts with — so it never surfaces as a
key event there. The chord goes dead in those terminals but can never
misfire or steal a cursor key.

Also updates the footer hint to advertise both directions.

* Lead the ca Manage tree with agents instead of projects

The Manage screen is about cloud agents, but they rendered at depth 3 of a
workspace > project > environment > agent > session tree — two container
levels to expand before reaching the thing the screen exists to show, with
the environment level almost always a single-child passthrough.

Invert the presentation: every environment that has agents is promoted to an
always-expanded top-level group, labelled with its project (plus /env when
the environment isn't production and the project has several). Agents render
directly beneath with their status glyphs, sessions still nested under their
agent. Groups sort by liveliness — anything running floats up — then default
project, then name; the cursor is anchored by row identity so re-sorts never
move the selection.

Projects with agent-less environments wait in a collapsible "other projects"
tail, default project first and undimmed. The tail opens itself while there
are no agents to show (so a fresh account still gets the browse tree, behind
a hint) and folds away once groups exist; toggling it by hand sticks. Every
environment stays reachable there for n, t, and r — including empty ones in
projects whose other environments have agents.

Refreshes no longer blank a loaded environment while the reply is in flight,
so groups stay put through the wake/sleep watch polls, and a failed refresh
keeps the stale list with the error in the status line instead of hiding
agents that exist. The data model and RowKind index tuples are unchanged —
this is a rework of the rows() flattener, the expansion handlers, and the
rendering; myCloudAgents already delivered everything at startup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/skip Author no release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant