fix(admin): lazy-load Media Library thumbnails#1827
fix(admin): lazy-load Media Library thumbnails#1827marcusbellamyshaw-cell wants to merge 5 commits into
Conversation
🦋 Changeset detectedLatest commit: d0547c3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
|
This PR has been inactive for 14 days. It will be closed automatically in 7 days if there is no further activity. If you're still working on this, please push an update or leave a comment. |
There was a problem hiding this comment.
This is the right fix for the reported problem: native loading="lazy" directly cuts the first-paint image request storm, and dropping the initial page size from 100 to 40 is a sensible complement that also lowers DOM and data cost. The implementation is minimal and safe — I don't see logic bugs, security issues, or backwards-incompatibility concerns.
What I checked:
- The four changed
<img>tags inMediaLibrary.tsx(local + provider grid/list) now carryloading="lazy". - The
limit: 40change inrouter.tsxis a straightforward constant change; cursor pagination still works. - The targeted tests pass according to the PR output (I didn't run them; verified statically).
- No new user-facing strings, no Tailwind direction issues, no SQL/API/auth changes, and a changeset is present.
Headline gaps:
- Provider thumbnails are untested. The PR touches
ProviderGridItemandProviderListItembut the new tests only assert lazy loading for the local grid/list views. AGENTS.md expects TDD for bugs, so provider cases should be added. - Stale test comment. The new grid test still says "A library page can hold up to 100 items", but the page size was reduced to 40.
MediaPickerModaluses the same thumbnail pattern without lazy loading. That's a natural follow-up for consistency once this lands.
One subtle thing to keep an eye on: provider thumbnails also fire onLoad to backfill missing width/height for providers that don't return dimensions. With lazy loading, that backfill won't happen until the image scrolls into view, so selecting an off-screen provider item before its thumbnail loads could leave the detail panel's dimensions line empty until reselection. If providers usually send dimensions, this is a non-issue.
| await screen.getByRole("button", { name: "List view" }).click(); | ||
| await expect.element(screen.getByAltText("pic.jpg")).toHaveAttribute("loading", "lazy"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
[needs fixing] The PR adds loading="lazy" to all four thumbnail <img> tags, including ProviderGridItem and ProviderListItem, but the new tests only assert it for the local grid and list views. Per AGENTS.md's "TDD for bugs" convention, the provider paths should have matching regression coverage too.
Add provider-tab tests analogous to the local ones. This will need fetchMediaProviders and fetchProviderMedia to be imported from ../../src/lib/api so they can be mocked per-test.
| }); | |
| }); | |
| describe("provider thumbnails", () => { | |
| it("provider grid thumbnails are natively lazy-loaded", async () => { | |
| vi.mocked(fetchMediaProviders).mockResolvedValue([ | |
| { | |
| id: "demo", | |
| name: "Demo Provider", | |
| capabilities: { browse: true, search: false, upload: false, delete: false }, | |
| }, | |
| ]); | |
| vi.mocked(fetchProviderMedia).mockResolvedValue({ | |
| items: [ | |
| { | |
| id: "1", | |
| filename: "provider.jpg", | |
| mimeType: "image/jpeg", | |
| previewUrl: "https://example.com/provider.jpg", | |
| }, | |
| ], | |
| }); | |
| const screen = await renderLibrary(); | |
| await screen.getByRole("button", { name: "Demo Provider" }).click(); | |
| await expect | |
| .element(screen.getByAltText("provider.jpg")) | |
| .toHaveAttribute("loading", "lazy"); | |
| }); | |
| it("provider list view thumbnails are natively lazy-loaded", async () => { | |
| vi.mocked(fetchMediaProviders).mockResolvedValue([ | |
| { | |
| id: "demo", | |
| name: "Demo Provider", | |
| capabilities: { browse: true, search: false, upload: false, delete: false }, | |
| }, | |
| ]); | |
| vi.mocked(fetchProviderMedia).mockResolvedValue({ | |
| items: [ | |
| { | |
| id: "1", | |
| filename: "provider.jpg", | |
| mimeType: "image/jpeg", | |
| previewUrl: "https://example.com/provider.jpg", | |
| }, | |
| ], | |
| }); | |
| const screen = await renderLibrary(); | |
| await screen.getByRole("button", { name: "List view" }).click(); | |
| await screen.getByRole("button", { name: "Demo Provider" }).click(); | |
| await expect | |
| .element(screen.getByAltText("provider.jpg")) | |
| .toHaveAttribute("loading", "lazy"); | |
| }); | |
| }); |
| }); | ||
|
|
||
| it("grid thumbnails are natively lazy-loaded", async () => { | ||
| // A library page can hold up to 100 items; without `loading="lazy"` the |
There was a problem hiding this comment.
[suggestion] This comment now describes the old page size. The initial limit was reduced from 100 to 40 in packages/admin/src/router.tsx, so the comment is misleading.
| // A library page can hold up to 100 items; without `loading="lazy"` the | |
| // A library page can hold up to 40 items; without `loading="lazy"` the |
Addresses emdashbot review on emdash-cms#1827: ProviderGridItem/ProviderListItem thumbnails were untested, and the grid test comment still cited the old 100-item page size (now 40).
e9af783 to
a1a7db9
Compare
Addresses emdashbot review on emdash-cms#1827: ProviderGridItem/ProviderListItem thumbnails were untested, and the grid test comment still cited the old 100-item page size (now 40).
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
Media Library page fetched 100 items on first load and rendered every thumbnail eagerly, firing ~100 concurrent resize requests through the Astro image endpoint on mount. Adds loading="lazy" to all thumbnail <img> tags and trims the initial page size to 40. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses emdashbot review on emdash-cms#1827: ProviderGridItem/ProviderListItem thumbnails were untested, and the grid test comment still cited the old 100-item page size (now 40).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
a1a7db9 to
39f0502
Compare
List view and provider tabs are rendered by Kumo's Tabs component
(role="tab"), not plain buttons. The new lazy-load tests used
getByRole("button", ...) for them, so the click never resolved and
the test timed out instead of asserting anything.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
What does this PR do?
The admin Media Library page (
/_emdash/admin/media) loads and scrolls slowly on libraries with many assets. The first page fetches up to 100 items, and every thumbnail<img>(grid + list views, local + provider tabs) rendered withoutloading="lazy", so the browser fired every thumbnail's resize request on mount instead of only the visible ones. Each resize also round-trips through Astro's_imageendpoint, so 100 concurrent requests stack up real cost on first paint.This PR adds native
loading="lazy"to all four thumbnail<img>tags inMediaLibrary.tsxand drops the initial page size from 100 to 40 (router.tsx) — existing cursor-based "Load More" pagination covers the rest.Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (targeted:packages/admin/tests/components/MediaLibrary.test.tsx)pnpm formathas been runAI-generated code disclosure
Screenshots / test output