Repository navigation
Stop iOS paragraphs drawing as one clipped line - #142
Merged
Merged
Conversation
On iOS, React Native draws text in a TextKit box exactly the size of the view's frame. Yoga rounds each frame edge to the pixel grid and stores it as float32, so a paragraph on a fractional offset that crosses y = 1024, 2048... comes out about 0.0001 pt shorter than the height it measured. TextKit then fits one line fewer, and with NSLineBreakByClipping the paragraph lands on one line clipped at the edge, while the layout still reserves the full height. On the drink page the 1/3 pt hairlines in LockedSection and SpecRow (and on some phones the Service tiles) put everything below them on fractional offsets, so whichever section sat across y = 2048 broke. Position, not content, which is why it followed the last section around. The ds Text now gets a hundredth of a point of bottom padding on iOS. That makes its height fractional, and Yoga always rounds a fractional text height up, so every paragraph gets up to 1/3 pt of slack wherever it lands. Web and Android are unchanged. Upstream fix: react/yoga#2011 (open). components/ds/textSlack.check.ts models Yoga's rounding and checks every text style on every grid offset down a 5000 pt page on 2x and 3x screens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Change routing:
|
This branch was successfully deployed
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
On iOS, the drink page's last section (on main, Version of) sometimes drew its paragraph as one line running off the right edge ("...rank it with every otl"), with a blank line of reserved height under it. Web was fine.
Root cause
It isn't the body's
gap, themarginTop: -space.xxlwrapper, or measurement caching. It's a React Native / Yoga rounding bug that only bites a paragraph at certain positions:RCTTextLayoutManager drawAttributedString:... frame:).roundLayoutResultsToPixelGrid). When the top is on a fractional offset (a 1/3 pt grid on 3x screens) and the paragraph crosses a power of two in absolute y (1024, 2048...), the two edges carry different float errors. The frame then comes out 47.99988 pt for 48 pt of text.NSLineBreakByClippingfor text with no line limit, the whole paragraph lands on one clipped line.On the drink page, the 1/3 pt hairlines in
LockedSection(Service) andSpecRowput everything below them on fractional offsets. On a 393 pt phone the Service tiles (40% basis,aspectRatio: 1) do it too. So whichever section sat across y = 2048 broke, which is why it followed the last section around rather than its content. An empty<View />after it can't help, because it doesn't move the paragraph.Upstream: react/yoga#2011 (open), react/react-native#57920. React Native's iOS core ships prebuilt, so patching Yoga in
node_moduleswouldn't compile in.The fix
The ds
TextgetspaddingBottom: textSlack(0.01 pt) on iOS only. That makes every text height fractional, and Yoga always rounds a fractional text height up ("never round down text"). So each paragraph gets up to 1/3 pt of slack wherever it lands: invisible, and far more than the float error. It's in the shared component, so every redesigned screen is covered, includingPublicShell(same hero + body layout). No redesigned screen draws text outside dsText. Web and Android are untouched. It's markedponytail:to drop once RN ships the Yoga fix. JS only, so it can go out over the air.components/ds/textSlack.check.tsmodels Yoga's rounding. It reproduces the device's numbers exactly (47.99993896484375 and 47.9998779296875) and checks every text style, 1 to 4 lines, on every grid offset down a 5000 pt page on 2x and 3x screens. It fails if the slack is set to 0.Verified
iOS simulator (my own iPhone 17 Pro, iOS 26.5, fresh dev client from this branch, local Supabase, House Martini at Little Rye as Admin, full reloads only):
Web at 390 px: Version of wraps to two lines (358 x 48), and ds text has
padding-bottom: 0px. At 1280 px (desktop layout with sidebar): the paragraph fits on one line (539 x 24) with no clipping.Checks:
npm run typecheck(with.expo/typesmoved aside, as in CI),npm run lint(0 errors, no new warnings),npm run check:design,npm run test:unit(45/45 checks, including the new one) and Jest (12 suites, 81 tests) all pass. Not run locally:build:webandtest:security(no DB or RLS change); CI runs both.🤖 Generated with Claude Code