fix(mobile): stop pairing QR column from starving CJK copy (#9700) (#9711)

Pin the large-QR grid track to a shared size token so long under-QR copy
(relay-degraded notice) cannot max-content the auto column and collapse
the pairing step to one glyph per line. Constrain the QR stack for wrap
and lock the layout contract in tests.
This commit is contained in:
OrcaWin 2026-07-21 03:39:05 -04:00 committed by GitHub
parent c24ebcade5
commit 7cf052bcab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 15 deletions

View File

@ -0,0 +1,33 @@
import fs from 'node:fs'
import { describe, expect, it } from 'vitest'
const mobilePageCss = fs.readFileSync(new URL('./mobile-page.css', import.meta.url), 'utf8')
describe('mobile page QR grid layout (#9700)', () => {
it('defines a shared large-QR size token used by the box and grid tracks', () => {
expect(mobilePageCss).toMatch(/--mp-qr-large-size:\s*184px/)
expect(mobilePageCss).toMatch(
/\.mobile-page-root \.mp-qr-large\s*{[^}]*width:\s*var\(--mp-qr-large-size\)/s
)
})
// Why: `auto` sized the QR track to the unwrapped relay-degraded notice and
// starved the copy column so CJK wrapped one glyph per line (#9700).
it('pins step and pairing QR columns to the QR size instead of auto', () => {
expect(mobilePageCss).toMatch(
/\.mobile-page-root \.mp-step2-layout\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s+var\(--mp-qr-large-size\)/s
)
expect(mobilePageCss).toMatch(
/\.mobile-page-root \.mp-pairing-layout\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s+var\(--mp-qr-large-size\)/s
)
expect(mobilePageCss).not.toMatch(
/\.mobile-page-root \.mp-(?:step2|pairing)-layout\s*{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s+auto/s
)
})
it('lets under-QR stack children shrink so long notices wrap inside the track', () => {
expect(mobilePageCss).toMatch(
/\.mobile-page-root \.mp-qr-stack\s*>\s*\*\s*{[^}]*max-width:\s*100%[^}]*min-width:\s*0/s
)
})
})

View File

@ -37,6 +37,7 @@
--mp-flow-card-border-width: 1px;
--mp-flow-card-inset: calc(var(--mp-flow-card-padding) + var(--mp-flow-card-border-width));
--mp-flow-shell-height: 400px;
--mp-qr-large-size: 184px;
--m-bg-base: #111111;
--m-bg-panel: #1a1a1a;
--m-bg-raised: #242424;
@ -583,14 +584,15 @@
.mobile-page-root .mp-qr-large {
box-sizing: border-box;
width: 184px;
height: 184px;
width: var(--mp-qr-large-size);
height: var(--mp-qr-large-size);
/* Why: keep img size in lockstep with the box (padding 10px each side). */
padding: 10px;
}
.mobile-page-root .mp-qr-large img {
width: 164px;
height: 164px;
width: calc(var(--mp-qr-large-size) - 20px);
height: calc(var(--mp-qr-large-size) - 20px);
}
.mobile-page-root .mp-qr-refreshing {
@ -615,6 +617,19 @@
flex-direction: column;
align-items: center;
gap: 10px;
/* Why: stack sits in a fixed QR grid track; fill the track so under-QR copy
has a definite width to wrap against. */
box-sizing: border-box;
width: 100%;
min-width: 0;
}
/* Why: align-items:center sizes children to max-content. Cap width AND set
min-width:0 so max-width can actually win over flex/grid min-size:auto and
long under-QR copy (relay-degraded notice, regenerate label) wraps. */
.mobile-page-root .mp-qr-stack > * {
max-width: 100%;
min-width: 0;
}
.mobile-page-root .mp-link-under {
@ -639,17 +654,24 @@
cursor: not-allowed;
}
/* Step 1 (Get the app) — borderless layout, page-1 typography */
/* Step 1 (Get the app) + Step 2 (Pair) — QR column width */
/* Why: pin the QR column to the QR's own width rather than `auto`. When the flow
card is narrow (mp-hero pins it to minmax(360px,560px)) an `auto` track grows
to its content's max-content the relay-degraded notice under the pairing QR
is an unwrapped single line starving the minmax(0,1fr) copy column to ~1 char
wide, so CJK copy wrapped one glyph per line (#9700). A fixed track stops that
starvation; .mp-qr-stack min-width:0 + max-width:100% (and the notice's
min-w-0 text span) stop the notice overflowing the track. */
.mobile-page-root .mp-step2-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr) var(--mp-qr-large-size);
gap: 28px;
align-items: start;
}
.mobile-page-root .mp-pairing-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr) var(--mp-qr-large-size);
grid-template-areas:
'copy qr'
'relay qr'
@ -661,10 +683,12 @@
.mobile-page-root .mp-pairing-copy {
grid-area: copy;
min-width: 0;
}
.mobile-page-root .mp-pairing-qr {
grid-area: qr;
min-width: 0;
/* Why: the QR belongs to the pairing heading, not the paragraph below it. */
margin-top: 48px;
}

View File

@ -138,9 +138,12 @@ describe('HeroFlow height', () => {
pairQrDataUrl: 'data:image/png;base64,qr',
relayDegraded: true
})
expect(screen.getByTestId('relay-degraded-notice')).toHaveTextContent(
'only works on your local network'
)
const notice = screen.getByTestId('relay-degraded-notice')
expect(notice).toHaveTextContent('only works on your local network')
// Why: wrap-capable text item inside the fixed QR track (#9700); bare text
// nodes in a flex row cannot shrink below max-content and overflow the track.
expect(notice.querySelector('.min-w-0')).not.toBeNull()
expect(notice.className).toMatch(/\bmin-w-0\b/)
expect(screen.getByText('Orca Relay is in beta.')).toBeInTheDocument()
})

View File

@ -290,14 +290,17 @@ export function HeroFlow({
</button>
{relayDegraded ? (
<p
className="flex items-start gap-1.5 text-xs text-muted-foreground"
className="flex w-full min-w-0 items-start gap-1.5 text-xs text-muted-foreground"
data-testid="relay-degraded-notice"
>
<CircleAlert className="mt-0.5 size-3.5 shrink-0" aria-hidden />
{translate(
'auto.components.mobile.MobileHero.relayDegradedNotice',
'Relay couldnt be reached — this code only works on your local network.'
)}
{/* Why: min-w-0 so the flex text item can wrap inside the fixed QR track (#9700). */}
<span className="min-w-0">
{translate(
'auto.components.mobile.MobileHero.relayDegradedNotice',
'Relay couldnt be reached — this code only works on your local network.'
)}
</span>
</p>
) : null}
</div>