fix(chat): the composer stays put — the hidden speaker label was stretching the page - #100
Merged
Merged
Conversation
…tching the page Past a conversation's first long turn, scrolling beyond the end of the transcript moved the whole chat: transcript, composer and status row slid up together over a blank page. The page is a 100vh shell and only #log is meant to scroll, so the composer should never move at all. The page itself had become scrollable. T4 (59777da) takes the speaker label — "You" / "LevelCode AI" — off the screen and keeps it for screen readers, with the usual rule for that: position: absolute, 1px, clipped. Nothing above the label was positioned, so its containing block was the page, not the transcript. Each label was laid out at its UNSCROLLED place in the log, where the log's scrolling and clipping do not reach it. A labelled message that started more than a viewport down had its label below the fold; the page grew to reach that one pixel; and once the log was at its end, the wheel scrolled the page. In practice: from the second turn of any conversation whose first one was long. #log is now positioned (position: relative), which makes it the containing block for everything inside it — this label, and anything taken out of flow later. The label is as it was: out of flow, clipped, and in the accessibility tree. Not done: locking the page with overflow: hidden. It would hide the symptom and leave the cause. And in a window too short for the composer and the status row — the shell needs 208px, measured in a 140px viewport — scrolling the page is the only way to reach the bottom controls. Verified in Chromium against the shipped chat.html, served the way the host serves it (CSP and nonce filled in, acquireVsCodeApi stubbed) and driven with the host's own messages: two agent turns, the first with a long code block, in a 1118px viewport. before after label's offsetParent <body> #log last label, its message at -22px 1962px -23px page height 1963px 1118px composer after 4 wheel ticks -400px unmoved transcript content height 2919px 2919px Scrolling up inside the transcript still pauses auto-scroll and shows "jump to latest". No console errors. webviewCss.test.js gains the guard no DOM test can give, since the fake DOM lays nothing out: #log is positioned and stays positioned, with the shell's premise pinned beside it. It fails on develop's chat.html, on position: static, and on a later rule that un-positions the log. 48 suites, 925 cases, 0 failing (scripts/test-extensions.sh). docs/CHAT-TYPOGRAPHY.md D6 records the correction and the measurements. Not run in the editor: this worktree has no vscode/ build. Not reproduced: a single run that works first and answers last. Tool activity folds into a compact group, so that transcript stays short; the trigger confirmed here is a later turn that starts below the fold.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The regression assertion accepts positioning values that would remove the transcript from the flex layout.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Fixes chat-page scrolling by anchoring absolutely positioned transcript labels to #log.
Changes:
- Positions the transcript as the labels’ containing block.
- Adds a CSS regression test.
- Documents the cause and Chromium measurements.
| File | Description |
|---|---|
extensions/levelcode-ai/media/chat.html |
Anchors hidden speaker labels within the transcript. |
extensions/levelcode-ai/test/webviewCss.test.js |
Adds the regression guard. |
docs/CHAT-TYPOGRAPHY.md |
Records the fix and measurements. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+540
to
+543
| assert.ok(position && position !== 'static', | ||
| '#log must be positioned (position: relative). It is the containing block for the clipped speaker\n' | ||
| + 'label; without it every label is laid out against the page, the page grows past the viewport, and\n' | ||
| + 'the whole chat — composer included — scrolls away. Got: ' + (position || 'no position at all')); |
Comment on lines
+192
to
+193
| `#log { position: relative }` makes the log the containing block for everything inside it: this | ||
| label, and anything out of flow added later. Measured the same way (Chromium, the shipped |
Comment on lines
+154
to
+155
| over a blank page. Positioned, the log is the containing block for every descendant: the label, | ||
| and whatever is added later. webviewCss.test.js fails if this goes. */ |
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 bug
Past a conversation's first long turn, scrolling beyond the end of the transcript moved the whole chat: transcript, composer and status row slid up together over a blank page. The page is a
100vhshell and only#logis meant to scroll, so the composer should never move at all.The cause
The page itself had become scrollable.
T4 (
59777da) takes the speaker label — You / LevelCode AI — off the screen and keeps it for screen readers, with the usual rule for that:position: absolute, 1px, clipped. Nothing above the label was positioned, so its containing block was the page, not the transcript. Each label was laid out at its unscrolled place in the log, where the log's scrolling and clipping do not reach it.A labelled message that started more than a viewport down therefore had its label below the fold. The page grew to reach that one pixel, and once the log was at its end, the wheel scrolled the page. In practice: from the second turn of any conversation whose first one was long.
The fix
#log { position: relative; }— the transcript becomes the containing block for everything inside it: this label, and anything taken out of flow later. The label itself is unchanged: out of flow, clipped, and in the accessibility tree.Not done:
overflow: hiddenon the page. It would hide the symptom and leave the cause. And in a window too short for the composer and the status row — the shell needs 208px, measured in a 140px viewport — scrolling the page is the only way to reach the bottom controls.Verification
In Chromium against the shipped
chat.html, served the way the host serves it (__CSP__and__NONCE__filled in,acquireVsCodeApistubbed) and driven with the host's own messages: two agent turns, the first with a long code block, in a 1118px viewport.offsetParent<body>#logtest/webviewCss.test.jsgains the guard no DOM test can give, since the fake DOM lays nothing out:#logis positioned and stays positioned, with the shell's premise pinned beside it. It fails ondevelop'schat.html, onposition: static, and on a later rule that un-positions the log../scripts/test-extensions.sh: 48 suites, 925 cases, 0 failing.docs/CHAT-TYPOGRAPHY.mdD6 records the correction and the measurements.Not run in the editor. The worktree this was written in has no
vscode/build, so the fix has been seen in Chromium, not in the app.Not reproduced: a single run that works first and answers last. Tool activity folds into a compact group, so that transcript stays short; the trigger confirmed here is a later turn that starts below the fold.