Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report
Generated by PR Enrichment workflow | View current CI run |
There was a problem hiding this comment.
This pull request needs an owner decision before it can be approved, and it has independent fixes to make either way.
Owner decision: no Lightbox requirement yet covers gallery pages sharing one caption row, and this change pursues that direction while the design decision is open. The head also adds new requirements to the Lightbox record: AR9, the controls' backing and contrast with zoom on; and the zoom-settle formula with its motion roles. Approval needs a spec-gate owner to settle these: either approve the shared caption row and how it reserves its height (which today conflicts with the style-authoring rule that content renders once), or keep each caption under its own photo. An engineering owner records the decision by commenting /approve-spec <full head SHA>, and the author may do so as an engineering owner.
Blocking findings:
- The caption row renders hidden copies of the unmounted pages' captions to reserve its height, which the component style-authoring architecture's INV7 ("Content renders once") rules out unless an owner amends it.
- Against
mainthe diff is 8,108 authored lines, because the three stacked pull requests ride along with this 250-line change, so land them first or retarget so this pull request carries only its own commit. - In a gallery with
hasZoom, a click or tap on the reserved caption band under a photo without a caption no longer closes the viewer, whilemainand a gallery without zoom close it (useZoomStage.ts, the stage's tap only counts points inside the stage). .changeset/lightbox-gallery-paging.mdstill says each caption sits "right under its photo", but it now sits at the bottom of the stage, 252 px below a landscape photo on a phone.- The Lightbox record's FR5 requires the page settle to play named motion roles, and the head records it as playing the pager's own values instead, so register those values as roles or record an owner exception.
[Automated review]
8004b0f to
f53416e
Compare
There was a problem hiding this comment.
This pull request needs an owner decision before it can be approved, and it has independent fixes to make either way.
Owner decision: no Lightbox requirement covers gallery pages sharing one caption row, and the head also adds new requirements to the Lightbox record: AR9 (the controls' backing and contrast with zoom on) and the zoom-settle formula with its motion roles. A spec-gate owner needs to settle these: approve the shared caption row and how it reserves its height, which today conflicts with the style-authoring rule that content renders once, or keep each caption under its own photo. An engineering owner records the decision by commenting /approve-spec <full head SHA>, and the author may do so as an engineering owner.
Blocking findings:
- The caption row renders hidden copies of the unmounted pages' captions to reserve its height (
Lightbox.tsx, the caption sizer), which the component style-authoring architecture's INV7 ("Content renders once") rules out unless an owner amends it. - Desktop behavior change needs an owner decision: a mouse drag or a trackpad's sideways scroll now pages every gallery, with default props (
Lightbox.tsx, the pager'sallowedPointerTypes), so gate it to touch input or cite the decision that covers it. - Against
mainthe diff is 8,175 authored lines, because the three pull requests below ride along with this 294-line change, and their bodies carry a different stack line, so land them first or make the stack declaration identical in all four. - The Lightbox record's FR5 requires the page settle to play named motion roles, and the head records it as playing the pager's own values instead, so register those values as roles or record an owner exception.
[Automated review]
f53416e to
e44bf26
Compare
There was a problem hiding this comment.
This pull request needs a spec owner's decision before it can be approved, and it has independent fixes to make either way.
Spec decision: no Lightbox requirement covers gallery pages sharing one caption row, and the head adds new requirements to the Lightbox record: AR9 (the controls' backing and contrast with zoom on), the zoom-settle formula, and its motion roles. A spec-gate owner needs to approve the shared caption row and how it reserves its height, which today conflicts with the style-authoring rule that content renders once, or keep each caption under its own photo, and approve or reject the new record text. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.
Blocking findings:
- The caption row renders hidden, inert copies of the unrendered pages' captions to reserve its height (
Lightbox.tsx, the caption sizer), which the component style-authoring architecture's INV7 ("Content renders once") rules out unless an owner amends it. - Desktop behavior change needs an owner decision: with default props a mouse drag or a trackpad's sideways scroll now pages every gallery (
Lightbox.tsx, the pager'sallowedPointerTypes), and with zoom a ctrl-wheel zooms the photo and a released mouse drag coasts (useZoomStage.ts), so gate these to touch input or cite the decision that covers them. - Against
mainthe diff is 8,172 authored lines, because the three pull requests below ride along with this 294-line change and the stack line is not identical in every listed pull request, so land them first or make the stack declaration hold. - The Lightbox record's FR5 requires the page settle to play named motion roles, and the head records it as playing the pager's own values instead, so register those values as roles or record an owner exception.
[Automated review]
e44bf26 to
c98544b
Compare
There was a problem hiding this comment.
This pull request needs a spec owner's decision before it can be approved, and it has independent fixes to make either way.
Spec decision: no Lightbox requirement covers gallery pages sharing one caption row, and the head adds new requirements to the Lightbox record: AR9 (the controls' backing and contrast with zoom on), the zoom-settle formula and its motion roles. A spec-gate owner needs to approve the shared caption row and how it reserves its height, which today conflicts with the style-authoring rule that content renders once, or keep each caption under its own photo, and approve or reject the new record text. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.
Blocking findings:
- The caption row renders hidden, inert copies of the unrendered pages' captions only to reserve its height (
Lightbox.tsx, the caption sizer), which the component style-authoring architecture's INV7 ("Content renders once") rules out unless an owner amends it. - The Lightbox record's FR5 requires the page settle to play named motion roles, and the head records it as playing the pager's own values instead, so register those values as roles or record an owner exception.
- Against
mainthe diff is 8,442 authored lines, because the three pull requests below ride along with this 294-line change and #8215 does not contain #8197's head, so land the lower pull requests first or make every listed head contain the one below it. - Regression: the Chinese
hasZoomdescription still says every zoomed drag pans with momentum, while a mouse drag now stops at the release, so narrow it as the English description was narrowed.
Drag paging is now limited to touch input, so a mouse and a trackpad page a gallery as they do on main.
[Automated review]
c98544b to
20b897a
Compare
There was a problem hiding this comment.
This pull request needs a spec owner's decision before it can be approved, and it has independent fixes to make either way.
Spec decision: no Lightbox requirement covers gallery pages sharing one caption row, and the head adds new requirements to the Lightbox record: AR9 (the controls' backing and contrast with zoom on), the zoom-settle formula and its motion roles, beside unrecorded video and index behaviours. A spec-gate owner needs to approve the shared caption row and how it reserves its height, which today conflicts with the style-authoring rule that content renders once, or keep each caption under its own photo, and approve or reject the new record text. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.
Blocking findings:
- In a captioned gallery without zoom, a tap or a mouse click on the dark band between a photo and its caption, which now sits in the bottom row, leaves the viewer open, while
mainand a zoomable gallery close on it, because the hit test inLightbox.tsxtreats one box spanning photo and caption as the media. - The caption row renders hidden, inert copies of the unrendered pages' captions only to reserve its height, which the component style-authoring architecture's INV7 ("Content renders once") rules out unless an owner amends it.
- The Lightbox record's FR5 requires the page settle to play named motion roles, and the head records it as playing the pager's own values instead, so register those values as roles or record an owner exception.
- Against
mainthe diff is 8,622 authored lines, because the three pull requests below ride along and #8215 does not contain #8197's head, so land the lower pull requests first or declare a stack line that every listed head satisfies. - Miss: the caption sizer picks captions with a truthiness test (
item.caption && …) instead ofisRenderable, so a0caption reserves no height and atruecaption reserves an empty box.
The Chinese hasZoom description now matches the English one.
[Automated review]
20b897a to
86d0dd1
Compare
Each page of a Lightbox gallery laid out its own photo and caption, so a neighbour with a longer caption arrived with a smaller photo. Every page now has a caption band two caption lines tall under its photo, whatever the caption, so the photo area is the same on every page and paging never resizes it. A page with no caption keeps the band's space, so its photo sits where every other page's does, but it shows no backing there. A longer caption shows two lines, and its second line ends in a "more" button, so the caption keeps the band's whole width. The toggle is one line tall, so its fill stays on that line. The button opens the whole caption over the bottom of the photo, on the scrim the controls use, and its "less" closes it. An open caption too long for its panel scrolls, and it keeps the toggle's width clear at the end of every line. Paging closes it too. The whole caption is always the viewer's accessible description, clamped or open. The viewer lays out no hidden copies of other captions. Each caption renders the same elements in every state: without a toggle, the toggle's place in its last line has no size. The viewer renders inside MediaTheme, dark, so its controls, caption and counter take the theme's dark surface tokens and its rules for them, with no Lightbox-owned colors beyond the controls' and the caption's backing. The caption is body Text, both of its lines start at one edge, and "more" and "less" are a ghost Button. The band clears the screen's bottom edge and its safe area. Focus the viewer moves follows the last input, so a tap that opens it paints no ring. The caption sits on its own backing, the controls' backing colour at 90 %, so it keeps 4.5:1 over any photo or backdrop behind it. With zoom, the controls' icons and the Counter's text keep the on-dark colour, whatever a theme does to buttons or Text. An open caption is the clamped caption itself, unclamped over the photo's bottom, so the caption renders once. In a gallery the keyboard's zoom button is the current page's photo box, so the caption band and its button sit outside it, and Enter or Space on "more" toggles the caption. Paging from a focused "more", "less" or photo moves focus to the new page's photo, or to Close without zoom, so the arrow keys keep paging. "Less" keeps its focus.
The zoom pan's begin threshold was set by Lightbox's own capture pointerdown listener on the stage. The shared PanGestureRecognizer takes a per-press hysteresis function, so the threshold moves there and the listener goes. A mouse's or a pen's drag on a zoomed photo still pans from its first move, and a finger keeps UIKit's threshold. The zoom stage's header now says that the return past a limit springs from the pinch's velocity. A fine-pointer test's comment splits one 26-word sentence in two, under the 25-word limit.
|
/approve-spec c56f4e1 |
c56f4e1 to
0fc994c
Compare
|
/approve-spec 0fc994c |
There was a problem hiding this comment.
Changes requested: the record text is approved for this head, and four blocking issues remain.
This pull request was reviewed on its own because its Stack: line no longer holds: #8148 has merged, and #8197's current head is not contained in this branch.
- Miss: A gallery caption that fills its two lines and then grows while the viewer is open never shows "more", because Lightbox re-measures the clamp only on resize, attach and open or close instead of using
useTruncation, which also watches content changes (Lightbox.tsx:862-899). - Miss:
useZoomStage.ts:288-311adds its own capturepointerdownlistener to set the pan's begin threshold, which the sharedPanGestureRecognizeralready takes as a per-presshysteresisfunction, so pass it there and drop the listener and its gap sentence, as #8197's current head does. - Miss: A single photo's caption has no backing and measures 4.0:1 on the default backdrop over a white page, under the 4.5:1 that FR17's verification row requires, while the caption-row changeset claims 4.5:1 over any photo, so give it the caption's backing or narrow the row and the changeset to gallery captions.
- Regression: The branch no longer merges into
main(13 conflicting files, includingLightbox.tsxanduseZoomStage.ts) and, with the stack broken, carries 10,763 authored lines as one change, so rebase #8197 ontomainand this commit onto #8197.
CI has not built or tested this head.
[Automated review]
This merge takes main at bef866e, with the Popover group, and this pull request's own diff is unchanged.
The zoom stage's pan refused a finger's drag that headed past a zoomed photo's edge in every Lightbox, so a single photo resting at its edge no longer rubber-banded there. Only a gallery's touch pager takes that drag now: the stage yields at the edge only when Lightbox says an outer pager is there. A single photo gives past its edge as on main. The record's press-tracker line no longer names the stage's own pointerdown listener, which the pan's per-press threshold replaced.
The spec owners ruled this a consistency fix: AST-108 plays motion at the theme's tempo, and AST-094 requires the touch presentations to follow it, but the pager's settle after a release ignored it. solvePaging now takes the tempo. Each step lasts tempo times as long and carries tempo times the release's distance, so a settle from rest keeps its shape and takes tempo times as long, and a release still starts at 96 % of the finger's speed. At tempo 1 the settle is unchanged. No motion role names the paging table, so usePager reads the tempo of the theme the track is in. Every pager follows it: a Lightbox gallery and a Carousel with hasSnap. The busy-main-thread proof of the gallery's settle now plays at tempo 3, as the zoom's does. A swipe at tempo 1 checks that the slowed settle is three times as long, and a frozen settle and one that the main thread drives both fail the proof. A refused drag's spring-back is now read from its keyframes rather than counted in main-thread frames, which a loaded runner thins out.
A gallery caption re-measured its clamp only when its box resized, a page attached, or the caption opened or closed. New text in a box that kept its two lines never got "more". The clamp now comes from Text's own truncation check, which also follows new text: useHeldTruncation, a variant of useTruncation internal to core, keeps the answer while the caption is open, so the toggle stays mounted, with its focus, as the caption closes. useTruncation's public options do not change, and only the held variant runs a layout effect, so Text and Heading run no new effect. Lightbox still measures the toggle's place and the open caption's scroll, now on new text too. A closed dialog lays its caption out at no size, so Lightbox measures the caption again as the dialog opens, before the first paint: the first frame that shows a clamped caption already ends it in "more". The changeset now says that a gallery's caption keeps 4.5:1, and the record lists a single photo's caption, which has no backing, as a gap in FR17.
|
/approve-spec c83f03d |
There was a problem hiding this comment.
Changes requested: the record text is approved for this head, and two blocking issues remain.
- The
Stack:line does not hold, because #8147's and #8148's bodies carry a different line, #8148's head is not in this branch after its squash merge, and the line branches at #8197, so this pull request is reviewed on its own and carries #8197's increment as 5,870 authored lines with no exemption; put one identical, linearStack:line naming only the open pull requests (for example#8197 → #9810 → #10284) in every listed body. - Regression: since #10301 landed on
main, the branch conflicts inpackages/core/src/Lightbox/LightboxFinePointer.a11y.browser.spec.tson theexpectSpringZoomcomment this pull request rewords (lines 429-430), so mergemainand keep its comment.
The caption band itself is unchanged since the last round and has no other blocking finding. Two non-blocking notes: spec:AST-094's FR45 verification row still lists tempo as a pager gap, and FR17's review state should read settled; implementation gap now that a gap names it. CI has not run on this head: it skips every job while the pull request is a draft.
[Automated review]
| | FR14 | Gestures, paging, motion, and reduced motion run through Core's one shared set of internal touch modules; Lightbox bundles no copy of a module's primitive and adds no gesture listener outside them. | `architecture:react-component-runtime` INV13 | settled; implementation gap | | ||
| | FR15 | Lightbox supports a Picture-in-Picture window as its only second document; there it ranks Escape and platform close among that window's members. | `family:overlay-dismissal` FR8 | settled | | ||
| | FR16 | Under a coarse primary pointer, a close that is not a drag down (FR8), a pinch in (FR9), or a return into a source (FR12) plays the touch exit: Close, Escape, a platform close, and a tap beside the photo while the controls are shown (ORD3). The photo shrinks a little and fades out in place with the backdrop and controls, as the native viewer's zoom transition does without a source, on a spring from rest, `springs.settle` unless the motion table names another role, with the backdrop and controls fading on its pace (`spec:AST-094` FR7, FR45). `onOpenChange(false)` reports at the request, and the viewer takes no input while the exit plays. Under a fine pointer the viewer closes at once (FR11). | `spec:AST-094` FR7 | settled; implementation gap | | ||
| | FR17 | The viewer is a media surface: it renders inside `MediaTheme` (dark), so its controls and caption take the theme's dark surface tokens and the active theme's component rules (`architecture:theme-application` INV8), with no Lightbox-owned colors beyond AR9's backing, the caption's backing and the on-dark color. | Specs decision, Nov 5 2026 | settled | |
There was a problem hiding this comment.
Non-blocking: the current-state impact now lists "Single photo's caption contrast (FR17)", so this row's review state should read settled; implementation gap, as FR4, FR14 and FR16 do.
The release note named a Carousel with hasSnap, but a plain hasSnap is
native CSS scroll snap. Only hasSnap={useSnapPaging()} runs the pager
whose settle now follows tempo, so the note names that value. The
AST-094 FR45 verification row no longer lists tempo as a gap of the
pager's settle.
This merge takes main at 1c142dc. meta#10301 rewrote the spring-zoom check in LightboxFinePointer.a11y.browser.spec.ts onto the shared interaction harness, and this pull request had split a sentence of that check's old comment. The merge keeps meta#10301's check and its comment as they are on main.
The check that Next and the arrow key show the next photo at once samples the track every frame for 600 ms after the press, and it needs more than 10 frames. WebKit in a 4-CPU container painted 3 to 7 frames in that time under load, so the check failed there. The sampler now runs for 600 ms and at least 12 frames. At half the test's timeout it gives up, and too few frames fail by name. The one-place check is the same.
There was a problem hiding this comment.
Approved pending CI: CI has not run on this head yet. As a draft from a fork, it runs once the pull request is marked ready and a maintainer adds the ci:run label.
This pull request's own increment over #8197 has no blocking findings: its record text is approved for this head, and the Stack: line now holds, so the earlier size finding no longer applies. A pull request in the stack lands only when it and every pull request below it are approved.
- Non-blocking: FR17's verification row names a black photo, but the contrast case runs only mid-grey and white; add it or drop it from the row.
- Non-blocking: FR17's review state should read
settled; implementation gapnow that the single-caption gap names it. - Note: the branch's conflict with
maininLightboxFinePointer.a11y.browser.spec.tscomes from #8197's rewordedexpectSpringZoomcomment and resolves when #8197 is restacked.
[Automated review]
| | FR11 | Fine-pointer browser tests | Double-click, click on the photo, drag, ctrl-wheel, trackpad scroll, mouse wheel notch | A double-click that does not zoom 2× about the point, a click that hides the controls, a fine-pointer close that animates, or a mouse wheel's notch that pans a zoomed photo fails. | `audit:Lightbox/touch` | | ||
| | FR6, AR1 | Touch test double-tapping a photo shaped like the screen and a landscape photo | 9:16 photo on a phone viewport, 3:2 photo | A double tap that zooms less than 2×, or not about the tapped point, fails. | `audit:Lightbox/touch` | | ||
| | FR16 | Touch tests closing by Close, Escape, platform close, and a tap beside the photo with the controls shown; Reduce Motion | With and without `experimental`; controls shown and hidden | A touch close without the exit, a literal timing, input reaching the viewer during the exit, or a tap beside the photo that closes while the controls are hidden fails. | `audit:Lightbox/touch` | | ||
| | FR17 | Unit: `Lightbox.test.tsx` media surface cases; browser: `LightboxPaging` and `LightboxZoom` interaction specs, Chromium and WebKit | Gallery with captions, with and without zoom; a single photo's caption; over a white, a mid-grey and a black photo (body 14.7:1 over white) | A viewer outside `MediaTheme`, a Lightbox-set text color, a caption or counter that is not `Text`, a toggle that is not a ghost `Button`, two start edges, or a caption under 4.5:1 fails. | `audit:Lightbox/theming` | |
There was a problem hiding this comment.
Non-blocking: this row names a caption over "a white, a mid-grey and a black photo", but the contrast case in LightboxZoom.interaction.browser.spec.ts runs only mid-grey and white. Add the black photo to that loop, or drop it from the row.
| | FR14 | Gestures, paging, motion, and reduced motion run through Core's one shared set of internal touch modules; Lightbox bundles no copy of a module's primitive and adds no gesture listener outside them. | `architecture:react-component-runtime` INV13 | settled; implementation gap | | ||
| | FR15 | Lightbox supports a Picture-in-Picture window as its only second document; there it ranks Escape and platform close among that window's members. | `family:overlay-dismissal` FR8 | settled | | ||
| | FR16 | Under a coarse primary pointer, a close that is not a drag down (FR8), a pinch in (FR9), or a return into a source (FR12) plays the touch exit: Close, Escape, a platform close, and a tap beside the photo while the controls are shown (ORD3). The photo shrinks a little and fades out in place with the backdrop and controls, as the native viewer's zoom transition does without a source, on a spring from rest, `springs.settle` unless the motion table names another role, with the backdrop and controls fading on its pace (`spec:AST-094` FR7, FR45). `onOpenChange(false)` reports at the request, and the viewer takes no input while the exit plays. Under a fine pointer the viewer closes at once (FR11). | `spec:AST-094` FR7 | settled; implementation gap | | ||
| | FR17 | The viewer is a media surface: it renders inside `MediaTheme` (dark), so its controls and caption take the theme's dark surface tokens and the active theme's component rules (`architecture:theme-application` INV8), with no Lightbox-owned colors beyond AR9's backing, the caption's backing and the on-dark color. | Specs decision, Nov 5 2026 | settled | |
There was a problem hiding this comment.
Non-blocking: the new "Single photo's caption contrast (FR17)" gap says FR17 is not met for a single photo, so this row's review state reads settled; implementation gap, as FR4, FR14 and FR16 do.
|
/approve-spec ced33fe |
There was a problem hiding this comment.
Changes requested: the record text is approved for this head, and one blocking issue remains.
- Regression: the
Stack:line#8197 → #9810 → #10284no longer holds, because this head merges #8197's new head and #10284's head does not contain it, so this pull request is judged on its own and carries #8197's increment as 5,865 authored lines with no exemption; update #10284 so its head contains this one.
The earlier conflict with main is resolved. Non-blocking: FR17's verification row names a black photo that no contrast case runs, and FR17's review state should read settled; implementation gap now that a gap names it.
[Automated review]
There was a problem hiding this comment.
Approved pending CI: CI has not run on this head yet, and as a draft from a fork it runs once the pull request is marked ready for review and a maintainer adds the ci:run label.
#8197
Approved at its current head, with no blocking findings.
#9810
Approved, with no blocking findings: the record text is approved for this head, and the Stack: #8197 → #9810 line now holds.
- Miss (non-blocking): this pull request deletes the
GalleryKeepsIndexstory's description that #8197 adds (Lightbox.stories.tsx:191), so Storybook's docs show that story unexplained. - Non-blocking: FR17's verification row names a black photo that no contrast case runs, and FR17's review state should read
settled; implementation gapnow that a gap names it.
Whole stack
No blocking issue across the two pull requests; each lands only when it and every pull request below it are approved.
[Automated review]
| }, | ||
| }; | ||
|
|
||
| export const GalleryKeepsIndex: Story = { |
There was a problem hiding this comment.
Miss (non-blocking): #8197 gives this story a description ("A controlled gallery whose parent keeps its index, paging only with its own buttons, say while a change is confirmed. A swipe returns to the photo the parent shows."), and this pull request deletes it, so Storybook's docs show the story with no explanation. Restore the comment above this line.
| <div | ||
| {...stylex.props(styles.captionFlow)}> |
There was a problem hiding this comment.
Non-blocking: component:Text says "Block-level content … is not supported inside a clamped Text, because it breaks the clamp." This block keeps the clamp in Chromium, and the browser specs measure it in WebKit; record the case with Text's owner so a change to Text does not break the caption silently.
Stack: #8197 → #9810
Stacked on #8197's 3ee5207, on main 1c142dc. This head merges #8197's head into the previous head c83f03d with no conflict, and this pull request's own diff is unchanged. Review this pull request's two commits: the caption band, vjeux@0fc994c, and the answers to CodeBunny's 10:27Z review, vjeux@c83f03d
The design owner's option C for a gallery's captions (card 46): a constant photo area; the caption in a fixed two-line band; a longer caption clamps, and "more" opens the full caption over the bottom of the photo on a scrim; the full caption is always in the accessible description; no hidden sizing copies.
It answers the design owner's re-check (card 46C (Cindy)): the caption sat too close to the bottom, its second line was indented, its size was unusual, and the Close button showed an outline on a tap's opening. The caption must also meet 4.5:1 over a mid-grey photo (Specs decision, Nov 5 2026). Lightbox renders through Astryx parts on a media surface:
MediaTheme(dark), so its controls, caption and counter take the theme's dark surface tokens and its rules for them. No Lightbox color is left beyond the controls' and caption's backing and the on-dark color (AR9).Textin the body type, so both caption lines start at one edge. Owner decision, Nov 5 2026 (Cindy): body text.Buttonone caption line tall, at the end of the caption's last line, so the caption keeps the band's whole width and the clamp's "…" comes just before "more" (card 46D). Its hit area reaches out toward 44 px under a finger, sideways and down, never into the line above. An open caption that scrolls in its panel keeps the toggle's width clear at the end of every line.This round answers the review on the rebased head: the toggle no longer sits inside the zoom button, "less" keeps its focus, and FR17 names the caption's backing. Out of scope: the backdrop keeps the page theme's
--color-overlayand blur, as today.::backdropinherits from the dialog element, outside the media surface.User impact
In a Lightbox gallery, every page has a caption band two caption lines tall under its photo, whatever its caption. So the photo area is the same on every page, and paging never resizes it. A caption longer than two lines shows two lines and a "more" button. "More" opens the whole caption over the bottom of the photo, on a dark backing, and "less" or paging closes it. Under a finger the button takes taps out toward 44 px, and its tap is its own, not the photo's. A screen reader gets the whole caption as the viewer's description, clamped or open.
Contrast
Measured on #10084's exact preview (4c07f5c, stacked on this pull request) in Chromium: default theme, a 402 px phone viewport, coarse pointer, text against what lies under it:
Across the shipped themes (butter, chocolate, gothic, matcha, stone), body stays at or above 7.3:1, matcha the lowest. Supporting in its secondary color falls to 2.7:1 in matcha and 3.8:1 in chocolate over a white photo; the other themes give 5.0–5.6:1.
Record change
component:LightboxFR1 gains: "In a gallery, Caption sits in a band two caption lines tall under every page's Media. A longer caption shows two lines and a "more" button that opens it in full over the bottom of the Media, on a scrim. The full caption is always the viewer's accessible description. The caption band uses Astryx parts: the caption text isTextwith the body type, and "more"/"less" is a ghostButton, not hand-styled elements."MediaTheme(dark), so its controls and caption take the theme's dark surface tokens and the active theme's component rules (architecture:theme-applicationINV8), with no Lightbox-owned colors beyond AR9's backing, the caption's backing and the on-dark color." Its basis reads "Specs decision, Nov 5 2026". Its verification row names the unit file and the browser interaction spec, and states the caption's contrast over a white, a mid-grey and a black photo (body 14.7:1 over white).spec:AST-038's existing-code row now reads: "Lightbox's caption renders through Text under MediaTheme (component:Lightbox)", with the rest of the row unchanged.Writing rubric results
Visual authority
DDrow reused or amended.Design decisionstable by a team design owner.docs/design/.The design owner chose option C on card 46 and asked for these fixes on card 46C; the record states them in FR1 and FR17.
How to check
State matrix
Minimal DOM
Element counts for the current page's caption, measured in Chromium on Storybook builds. Main renders a gallery's caption as one
Textunder the photo, with no clamp and no toggle.Every captioned page renders the same elements in every state. The toggle is the only element that comes and goes, and only while the caption runs past two lines. Each element's job:
aria-controls.Text: the caption itself, also the viewer's description.Button(4 elements with its label), only while the caption runs past two lines. It hosts its own position, so the slot that wrapped it is gone.Testing
At this head (ced33fe): the same own diff as c83f03d (patch-id unchanged), merged onto feat(Lightbox): swipe between gallery photos like Photos #8197's 3ee5207 without conflict.
At this head (c83f03d), CodeBunny's 10:27Z items and two pre-review follow-ups (vjeux@c83f03d):
useHeldTruncation, a variant ofuseTruncationinternal to core. It keeps its answer while the caption is open, so the toggle stays mounted, with its focus, as the caption closes.useTruncation's public options don't change, and plainuseTruncationruns no new effect, so Text and Heading pay nothing new.useTruncationthat adds one.pnpm install --frozen-lockfile0; Lightbox, Text and Heading units 476/476;pnpm check:knowledgeandpnpm check:changesetspass; coretsc0; strict ESLint 0; comment sentence lengths 0 over. Leak and public-text checks clean.LightboxPaging"the page settle keeps advancing every frame while the main thread is blocked" fails as on 0fc994c: feat(Lightbox): swipe between gallery photos like Photos #8197's tempo commit fixes it below this pull request.Red first:
At this head (0fc994c): Lightbox units 139/139; core
tsc0, strict ESLint 0, comment sentence lengths 0 over. On a Storybook build of this head: Chromium Lightbox a11y specs 38/38; WebKitLightboxViewportandLightboxKeyboard17/17; Lightbox Paging, Zoom and EveryPointer specs 78 passed in Chromium (3 WebKit-only skips); WebKit Paging, EveryPointer and Keyboard 38 passed (5 skips).Lightbox.tsx(the clamped caption isDIV(DIV(SPAN(),SPAN())), the one that fitsDIV()) and passes here.At c56f4e1: Lightbox units 138/138; core
tsc0, strict ESLint 0, comment sentence lengths 0 over, Prettier clean;pnpm check:knowledgeandcheck:repopass. On a Storybook build of this head: Chromium Lightbox a11y specs 38/38; Lightbox Paging, Zoom and EveryPointer specs 55 passed in Chromium (3 WebKit-only skips); WebKit Paging and EveryPointer plus theLightboxViewportcaption cases 34 passed (5 skips).LightboxViewport.a11y.browser.spec.ts: "a gallery page without a caption shows no caption backing, and every page's photo keeps its top" and "a clamped caption's "more" ends its second line, with no column of its own" (402 × 874 and 1440 × 900), and "… still ends its second line after the viewport narrows (402 → 360)" fail on 053f321's build and pass here. "An open caption scrolled to its end keeps every line beside "less" clear of it" (360 × 150, 390 × 150) fails on a build that keeps the inline place while the caption scrolls, and passes here. The unit case "follows the caption in reading order, and its place in the last line is hidden from assistive technology" fails on 053f321's product.iPhone 17 Pro simulator (iOS 26.2, Safari), on c56f4e1's Storybook build, every frame viewed: every page's photo top is 189 pt; page 1's two lines end in "…" and "more" (its last line ends at 318 pt, "more" sits at 328–390 pt on that line); page 2's two lines fit with no toggle; page 3 has no caption and no backing (
rgba(0, 0, 0, 0)); page 4's one line sits on its backing.c56f4e1's preview on the iOS 26.2 simulator (Safari, every frame viewed), pages 1 to 4:
Desktop, Chromium at 1440 × 900. Before (main, which has no captioned gallery story: "Gallery" page 1 and its captionless page 2, and "With caption" for a long caption):
After (c56f4e1, "Gallery with captions"): "more" collapsed, the same caption expanded, the captionless page, and a short caption:
At 053f321: core
tsc0, strict ESLint 0, comment sentence lengths 0 over, Lightbox units 137/137. Red first: the new case "returns focus to the opener without scrolling the page when the viewer closes" fails on 57425e7's product, whose close focused the opener withoutpreventScroll. On a Storybook build of this head: Chromium Lightbox a11y specs 31/31; Paging and EveryPointer 28 passed in Chromium (3 WebKit-only skips); Paging, EveryPointer and Keyboard 26 passed in WebKit (5 skips); the a11y audit finds 0 new violations;pnpm check:knowledgeandcheck:repopass.At 57425e7 (053f321 without the shared focus return): the LightboxKeyboard spec 9/9 in Chromium; Lightbox Paging and EveryPointer specs 28 passed in Chromium (3 WebKit-only skips), and Paging, EveryPointer and Keyboard 26 passed in WebKit (5 skips).
At 40e8001, the same behavior before the focus handoff moved to the shared helpers (the head also differs by the FR17 basis cell and two comments):
tsc0; strict ESLint on every changed file 0; comment sentence lengths 0 over;pnpm check:knowledgeandcheck:repopass (check:changesets,check:i18n-catalog,check:i18n-runtime,check:use-clientandcheck:package-boundariesat 65534b6, the same code without the viewport hook).Existing cases: the fine-pointer touch-action lists gain the media surface's
auto; the scroll case's viewport is 240 px tall so the body-size caption still overflows its panel; the paging specs find the pager's viewport bydata-lightbox-viewport, since the zoom button is now the photo box.iPhone 17 Pro simulator (iOS 26.2, Safari), on 4dded66's build, every frame viewed: a tap's opening leaves Close without a ring, and a key's opening shows it; the caption is body
Text(16 px in the default theme), its lines start at one edge, and the band keeps 12 px under them.