feat(protocol): honour viewport.resize end to end - #8
Merged
Conversation
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]>
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last piece of the "stop assuming 80 columns" thread, and the first one that crosses into Java.
viewport.resizehas been in the v1 protocol all along — declared on both sides, validated on arrival, and then dropped on the floor (RoutingMessageDispatcher→notImplemented). 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)auth.okandresume.ok. Sends before the socket opens are dropped (WsClient.sendhas 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, sogetBoundingClientRect()returns zeros.Server
viewportCols/viewportRows/setViewportonVoidCoreSession, defaulting to 80x24 — a client that never reports behaves exactly as before.Screen.onViewportResizedefaults to a no-op, deliberately notonEnter. Re-entering resets screen-local state: a resize mid-wizard would throw the user back to step 0 (WizardFormApp.onEnterreassignsstepIndex). A resize is not a navigation event.ScreenAppoverrides it to recompose and repaint, so mid-flow state survives a phone rotation.ctx.viewportCols()/viewportRows()for screens that want to size content.ctxsees the new width rather than the previous one — there's a test pinning that ordering.Verification
tsc --noEmitclean, bundle buildsNot 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.Flowstill defaults toDEFAULT_COLS = 80. Migrating flow-rendered screens ontoctx.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.