Reader: the tail is the paragraph rhythm, not a 30px reserve - #93
Merged
Conversation
> it seems like there is an additional newline at the end of the reader view. > just a bit too much whitespace imo. Confirmed, and measured against the rhythm above it rather than adjusted until it looked nicer. Scrolled to the end of a conversation that fills the pane, the space between the last line and the composer's border was 32px on desktop and 26px on a phone, while the paragraphs above it are 12px apart — 2.7× the spacing, about one blank line. Checked the causes that the mirror-image bug at the top of an expanded note turned out to have, and it is none of them: - not an uncollapsed margin: the last rendered block reports `margin-bottom: 0px`, which `.cx-md > :last-child` already takes care of; - not a trailing empty node from the renderer: nothing renders after the last paragraph at all — the tail holds zero elements; - not an element with no content — no spacer, no blank pending exchange, no working line with nothing to say, which was the shape of both earlier reports. It was `.cxv-body`'s own bottom padding: 30px, written into the same shorthand as the reading gutter, from when the reader's bottom edge was the pane's rather than a border 30px away. It is `--cx-tail: 12px` now — the same 12px that separates two paragraphs — so both viewports land on 14px (the tail plus `.cx`'s own 2px) and the end of the flow is spaced like the flow. The phone rule restated the whole shorthand to change the gutter, and carried a different bottom (24px) as a result. With the tail a variable it has nothing left to restate: it narrows `--cx-gutter` and nothing else, which is the same drift this file already learned about with the band's bleed. Neither of the two things that must not break did: - it still opens on the last exchange with the newest content on screen — `atBottom: 0`, last exchange fully visible, answer on screen, at 1180px and at 390pt; scrolling to the top and back returns to the end with the same 14px; - #78's needs-input band composes with it. That band is not on main, so I merged it in locally to check: it sits last inside the scroller with margins 8px/2px, so the gap below it is 2 + 12 = 14px. It carries no reserve of its own, and before this change it had 32px under it too. Pinned in `pendingExchange.test.mjs`: the tail comes from `--cx-tail`; its value equals `.markdown p`'s bottom margin, so the two cannot disagree without the test saying so; the phone rule restates no padding; and the last block still gives up its margin. Mutation-checked — restoring the 30px tail fails two checks, restating the phone padding fails a third. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Confirmed. Measured before/after →
Scrolled to the end of a conversation that fills the pane, the space between the last line and the composer's border was 2.7× the spacing between the paragraphs above it:
What it was — and the three things it wasn't
You were right to point at
633d492's class of cause, so I checked those first and each is ruled out by measurement, not by reasoning:margin-bottom: 0px—.cx-md > :last-childalready gives it up.It was
.cxv-body's own bottom padding:30px(24px on a phone), sitting in the same shorthand as the reading gutter:A number from when the reader's bottom edge was the pane's, before a composer sat under it with a border 30px away. So a margin fix would have been the wrong shape of fix — but so would trimming it by eye.
The fix, and where the number comes from
--cx-tail: 12px— the same 12px that separates two paragraphs (.markdown p { margin: 0 0 12px }). The end of the flow is then spaced like the flow: 14px in total on both viewports, the tail plus.cx's own 2px.The phone rule restated the whole shorthand in order to narrow the gutter, and carried a different bottom (24px) as a side effect. With the tail a variable it has nothing left to restate — it narrows
--cx-gutterand nothing else, which is the same drift this file already learned about with the prompt band's bleed in #71.The two things that must not break
atBottom: 0, last exchange fully visible, its answer on screen. Scrolling to the top and back returns to the end with the same 14px.refs/pull/78/headin locally to check (clean merge, nothing pushed). It sits last inside the scroller with margins8px/2px: 10px above it, and 14px below — its own 2px plus the tail, carrying no reserve of its own. Before this change that state had 32px under the band too, so the band was not the thing holding the extra space.Pinned
In
web/test/pendingExchange.test.mjs, where the reader's other CSS invariants live:--cx-tail, not a number in the shorthand;.markdown p's bottom margin — the relationship, not the coincidence, so the two cannot drift apart silently;Mutation-checked: restoring the 30px tail fails two of those, restating the phone padding fails the third.
Suites
Web green (143 checks), typecheck clean.
server/test/archive.test.mjsandresize.test.mjseach failed in a full run and behave identically on pristine main under the same load:archivepasses alone on both trees, andresizefails alone on both (2 failures, same assertions, with five other agents' servers running). Neither is related to this change, which is CSS in the reader.🤖 Generated with Claude Code