Skip to content

feat(protocol): honour viewport.resize end to end - #8

Merged
myrddian merged 1 commit into
mainfrom
feat/viewport-resize
Aug 1, 2026
Merged

feat(protocol): honour viewport.resize end to end#8
myrddian merged 1 commit into
mainfrom
feat/viewport-resize

Conversation

@myrddian

@myrddian myrddian commented Aug 1, 2026

Copy link
Copy Markdown
Owner

The last piece of the "stop assuming 80 columns" thread, and the first one that crosses into Java.

viewport.resize has been in the v1 protocol all along — declared on both sides, validated on arrival, and then dropped on the floor (RoutingMessageDispatchernotImplemented). The client never sent it either. So every session rendered against an assumed 80 columns regardless of the actual canvas.

Per ADR-035 this is v1.x behaviour work, not v2 — the message shape already exists, so nothing on the wire changes.

Client (src/viewport.ts)

  • Measures one character cell from a probe rendered in the region's own font, divides the main region by it, reports cols/rows.
  • Clamps to the server's validated range (20..500 / 10..500) so a very small or very large window can't trip a protocol error.
  • Debounces at 150ms and de-duplicates: a drag-resize fires a torrent of pixel events, but the cell count changes rarely, and every report costs a server-side repaint.
  • Re-reports on auth.ok and resume.ok. Sends before the socket opens are dropped (WsClient.send has no queue), and a resumed session may never have learned this client's size.

The arithmetic is split into a pure viewportFromMetrics() so it's testable without a layout engine — happy-dom doesn't do layout, so getBoundingClientRect() returns zeros.

Server

  • viewportCols / viewportRows / setViewport on VoidCoreSession, defaulting to 80x24 — a client that never reports behaves exactly as before.
  • Screen.onViewportResize defaults to a no-op, deliberately not onEnter. Re-entering resets screen-local state: a resize mid-wizard would throw the user back to step 0 (WizardFormApp.onEnter reassigns stepIndex). A resize is not a navigation event. ScreenApp overrides it to recompose and repaint, so mid-flow state survives a phone rotation.
  • ctx.viewportCols() / viewportRows() for screens that want to size content.
  • The router records the size before invoking the hook, so a screen composing against ctx sees the new width rather than the previous one — there's a test pinning that ordering.

Verification

  • Java: 610 tests, 0 failures (119 skipped — the Docker-dependent integration ones)
  • Frontend: 80 passing across 12 files, tsc --noEmit clean, bundle builds
  • 6 new Java tests, 9 new TS tests

Not verified: a real browser. No actual resize, rotation, or mobile keyboard has been exercised — the measurement path (measureCell) is the part happy-dom cannot cover.

Follow-up, not in scope

Layout.Flow still defaults to DEFAULT_COLS = 80. Migrating flow-rendered screens onto ctx.viewportCols() is the next change — that's what turns "the server knows your width" into "the server uses it". This PR lands the plumbing and the hook; nothing regresses for screens that ignore them.

The message existed in the v1 protocol, was validated, and the server
threw it away (`notImplemented`). The client never sent it either. So the
server rendered every session against an assumed 80 columns.

Client — src/viewport.ts:
- measures one character cell from a probe in the region's own font,
  divides the main region by it, and reports cols/rows
- clamps to the server's validated range (20..500 / 10..500) so an
  extreme window can't trip a protocol error
- debounces (150ms) and de-duplicates: a drag-resize fires a torrent of
  pixel events but the cell count rarely changes, and every report costs
  a server-side repaint
- re-reports on auth.ok and resume.ok — sends before the socket opens are
  dropped, and a resumed session may never have learned this client's size

Server:
- viewportCols/viewportRows/setViewport on VoidCoreSession, defaulting to
  80x24 so a client that never reports behaves exactly as before
- Screen.onViewportResize, defaulting to a NO-OP rather than onEnter.
  Re-entering resets screen-local state — a wizard would jump back to
  step 0 — and a resize is not a navigation event. ScreenApp overrides it
  to recompose and repaint, so mid-flow state survives a rotation.
- ctx.viewportCols()/viewportRows() for screens that want to size content
- the router records the size before invoking the hook, so a screen
  composing against ctx sees the new width, not the previous one

Layout.Flow still defaults to 80 columns; migrating flow-rendered screens
onto the session width is the follow-up. This lands the plumbing and the
hook, and nothing regresses for screens that ignore it.

Co-Authored-By: Claude Opus 5 <[email protected]>
@myrddian
myrddian merged commit 5187098 into main Aug 1, 2026
6 of 7 checks passed
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