Skip to content

Draw the terminal at the size of the pane it is in - #81

Merged
Alexgodoroja merged 1 commit into
mainfrom
terminal-fills-its-pane
Sep 9, 2026
Merged

Draw the terminal at the size of the pane it is in#81
Alexgodoroja merged 1 commit into
mainfrom
terminal-fills-its-pane

Conversation

@Alexgodoroja

Copy link
Copy Markdown
Collaborator

The terminal did not fill its pane

A viewer never changes how many columns and rows a session has — the relay picks the grid, the CLI opens the PTY at it. What a viewer chooses is how large to draw them, and that was a single knob: the font size, scaled until the grid fitted in whichever direction ran out first.

A pane is almost never the shape of a 120x36 grid, so on a wide one the columns stopped growing the moment the rows reached the bottom. Measured in a real layout at four window sizes, the grid used only 68–77% of the pane's width, and the text was a third smaller than there was room for.

window before after
1440x900 11.5px, 77% of the width 14.75px, 98%
1728x1000 13.5px, 71% 17.75px, 94%
1920x1080 14.75px, 68% 19.75px, 91%
2560x1440 21.25px, 70% 28.25px, 93%

What changed

fittedTerminal replaces fittedTerminalFontSize and returns two things instead of one:

  • The font comes from the width, where the columns are.
  • The rows are then spread down the full height with whatever leading is left over, between the font's own line box and the leading the surface asked for (1.35 in the app, the device's typography in the standalone viewer). A pane with height to spare keeps the leading it had.

Neither axis may overflow. The size is derived from what the browser says a character measures, then checked against what the emulator will actually lay out — columns rounded to whole CSS pixels, rows to whole device pixels at the ratio the screen will round to — and stepped down if it would not fit. A test sweeps 126 box/ratio combinations and asserts the drawn grid never exceeds the box.

All of it is client-side: no session, no relay and no PTY sees any of this.

Supporting changes:

  • web/terminal-metrics.ts, vendored into the app beside the fit and covered by npm run check:protocol, holds the two browser measurements — what a character occupies (the way xterm measures it, so the chosen size is the size that lands) and how much room the pane has, minus a scrollbar only where the platform actually takes one.
  • What the fit cannot use is now split between both sides of the pane instead of left down one of them.
  • Measuring directly retires FitAddon in the viewer, where proposeDimensions was the only thing it was used for. The app still ships it for the sign-in page's animated preview, which genuinely wants a grid that fills its box.

Also in here

The open tabs survive a reload. Refreshing put you back at the list with every terminal closed. The ids of the open tabs and the one in front are now remembered per account, and put back from the first session list the page receives — rebuilt from that list rather than from what was stored, so a restored tab is never a stale copy of a session, and one that has since ended does not come back. Nothing is written to the browser that the session list would not hand over anyway.

The board columns lose their box. Each column drew a border and a wash around cards that already carry both, so three short columns read as three mostly empty containers.

Checks

npm run typecheck, npx vitest run (20 files) and npm run build:web at the root; npm run typecheck, npm test (616 tests) and check:protocol in app/. The app's npm run build stops at check-bundle on this machine for want of Firebase values in .env.local, which is unrelated to the diff.

A viewer never changes how many columns and rows a session has: the relay
picks the grid and the CLI opens the PTY at it. What it chooses is how large
to draw them, and that was one knob — the font size, scaled until the grid
fitted in whichever direction ran out first. A pane is almost never the shape
of a 120x36 grid, so on a wide one the columns stopped growing as soon as the
rows reached the bottom: about a third of the pane stayed empty and the text
was a third smaller than there was room for.

The font now comes from the width, where the columns are, and the rows are
spread down the full height with the leading that is left over, between the
font's own line box and the leading the surface asked for. Neither axis may
overflow: the size is derived from what the browser says a character measures,
then checked against what the emulator will actually lay out, at the device
pixel ratio it will round to. On a 1440x900 window a 120x36 session goes from
11.5px filling 77% of its pane to 14.75px filling 98%, and what the fit cannot
use is now split between both sides rather than left down one of them.

The measurements move into terminal-metrics.ts beside the fit, vendored into
the app the same way, so both surfaces measure a character identically. Doing
it directly also retires FitAddon in the viewer, whose proposeDimensions was
the only thing it was used for.

Also: keep the open session tabs across a reload, and take the box off the
columns of the session board.

A reload closed every terminal that was open. The ids of the open tabs and the
one in front are now remembered per account, and put back from the first
session list the page receives — rebuilt from that list rather than from what
was stored, so a restored tab is never a stale copy, and a session that has
since ended does not return. Nothing is written to the browser that the list
would not hand over anyway.

Each board column drew a border and a wash around cards that already carry
both, so three short columns read as three mostly empty containers.
@Alexgodoroja
Alexgodoroja merged commit 061eb2f into main Sep 9, 2026
16 checks passed
@Alexgodoroja
Alexgodoroja deleted the terminal-fills-its-pane branch September 9, 2026 03:12
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