Repository navigation
Conversation
the provider loads figurelightbox (yet-another-react-lightbox, its zoom plugin and styles) with a dynamic import on a figure's first hover, focus or open, keeps the loaded component in state and mounts it from then on. react.lazy is not used: it suspends on first render even when the module is loaded, and react 19 then holds the reveal back for up to 300ms. a failed load cancels the pending open and is retried on the next hover, focus or open. usecloseonroutechange now takes an onclose callback, so the provider can also cancel an open still waiting on the load on navigation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The scoped loading change has no identified blocking issues, and added tests cover its principal lifecycle and navigation behaviors.
No open issues 🎉
What changed in this PR
Defers the figure lightbox, zoom plugin, and styles until a figure is hovered, focused, or opened, reducing initial downloads for pages using findable-ui’s figure components.
Changes:
- Adds an on-demand loader and optional preload action while keeping the loaded lightbox mounted for subsequent opens.
- Preserves navigation-based closing and cancels pending opens after navigation or loading failures.
- Adds tests for deferred loading, preloading, failure recovery, navigation cancellation, and reopening.
| File | Description |
|---|---|
tests/figureLightbox.test.tsx |
Tests loading, preloading, cancellation, retries, and reopening. |
src/components/common/FigureLightbox/provider/utils.ts |
Adds the dynamic lightbox loader. |
src/components/common/FigureLightbox/provider/types.ts |
Adds an optional context preload action. |
src/components/common/FigureLightbox/provider/provider.tsx |
Loads and mounts the lightbox on demand; cancels pending opens. |
src/components/common/FigureLightbox/hooks/UseCloseOnRouteChange/utils.ts |
Changes the navigation listener to accept a close callback. |
src/components/common/FigureLightbox/hooks/UseCloseOnRouteChange/types.ts |
Defines the callback-based hook contract. |
src/components/common/FigureLightbox/hooks/UseCloseOnRouteChange/hook.ts |
Subscribes the supplied close callback to navigation events. |
src/components/common/FigureLightbox/figureLightbox.tsx |
Preserves controller-based closing through the updated hook. |
src/components/common/FigureLightbox/components/LightboxFigure/hooks/UseRegisterFigure/types.ts |
Exposes an optional figure preload handler. |
src/components/common/FigureLightbox/components/LightboxFigure/hooks/UseRegisterFigure/hook.ts |
Passes the provider’s preload action to figures. |
src/components/common/FigureLightbox/components/LightboxFigure/components/Figure/figure.tsx |
Triggers preloading on pointer entry and focus. |
Review effort: Balanced
frano-m
marked this pull request as ready for review
October 23, 2026 08:40
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.
Summary
Closes #1146.
FigureLightboxProviderno longer ships the lightbox with the page.yet-another-react-lightbox, its zoom plugin, its styles and findable-ui's lightbox components now sit in their own chunk. That chunk is fetched the first time a figure is hovered, focused or opened.What changed
provider/utils.tsaddsloadFigureLightbox(), a dynamicimport("../figureLightbox"). The provider calls it from a newonPreloadcontext action and fromonOpen. It keeps the loaded component in state and mounts it from then on, so later opens behave exactly as before.onPreloadononPointerEnterandonFocus, so the chunk is usually loaded before the click. Touch firespointerenterbefore the click too.useCloseOnRouteChangenow takes anonClosecallback instead of the controller ref. The lightbox passes its controller'sclose, which also clearsinert, as before. The provider uses the same hook to cancel an open that is still waiting on the chunk when the route changes, because until then there is no lightbox to close itself.FigureLightboxContextActionsgains an optionalonPreload, so existing context values still type-check.Why
Every page importing the provider or
LightboxFiguredownloaded the whole lightbox, even if no figure was ever opened. In brc-analytics, every Learn page (one catch-all route) paid for it, although only blog posts use it, and mounting the provider at_applevel wasn't an option.Measured on brc-analytics (production
next build --webpack, published 57.2.0 vs this branch)The small increase on every page is webpack's runtime growing from 3.5 KB to 4.8 KB so it can load CSS for async chunks.
Open time: in a local static export, a first open with no hover took 32–42 ms and a reopen 22 ms. On brc main's dev server, the published version takes about 33 ms (first open) and 31 ms (reopen). A dev-server run with this branch measured 69 and 62 ms, but the machine was under heavy load during that run, so that comparison is unreliable.
Assumptions I made
React.lazy/Suspense: I tried them first, and they made the first open take ~460 ms instead of ~40 ms.lazysuspends on its first render even when the module is already loaded, and React 19 then holds Suspense reveals back for up to 300 ms. Keeping the component in state avoids that. The reason is in the provider's JSDoc.import()again. The bundler caches it, and React ignores the identical state update.FigureLightboxProviderandLightboxFigure.onPreloadis optional anyway.How to verify
Definition of done from #1146:
LightboxFiguredoesn't pull the lightbox or its CSS into the page's chunk.npm install --no-save <tarball of this branch>andnpm run build-local:brc.sites/brc-analytics/.next/build-manifest.json, the/learn/[...slug]entry should list no chunk containingyarl__and nostatic/cssfile.yarl__chunk under.next/static/chunks/should be an async one (7985.*.jsin my build)./learn/blog/genotyping-cyclospora-assessing-current-practices.yarlchunk or lightbox CSS is fetched._app) without a bundle cost on pages with no figures. The provider's own module graph no longer reaches the library. On a page with no figures nothing triggers a load, so the only cost is the provider, the context and the webpack runtime increase above.npx storybook buildsucceeds, and in the FigureLightbox "Group" story the lightbox chunk loads on click and the lightbox opens and steps.npm testpasses (932). New tests cover:🤖 Generated with Claude Code
🐦 Built with cc-claude-tools