Skip to content

fix(admin): lazy-load Media Library thumbnails - #1827

Closed
ColoradoClimbr wants to merge 12 commits into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/media-library-lazy-thumbnails
Closed

ColoradoClimbr wants to merge 12 commits into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/media-library-lazy-thumbnails

Conversation

@ColoradoClimbr

Copy link
Copy Markdown
Contributor

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 without loading="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 _image endpoint, so 100 concurrent requests stack up real cost on first paint.

This PR adds native loading="lazy" to all four thumbnail <img> tags in MediaLibrary.tsx and drops the initial page size from 100 to 40 (router.tsx) — existing cursor-based "Load More" pagination covers the rest.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (targeted: packages/admin/tests/components/MediaLibrary.test.tsx)
  • pnpm format has been run
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are wrapped for translation (n/a — no new strings)
  • I have added a changeset
  • New features link to an approved Discussion (n/a — bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Sonnet 5

Screenshots / test output

 Test Files  1 passed (1)
      Tests  17 passed (17)

@changeset-bot

changeset-bot Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7f3ee09

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
create-emdash Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/release-service Patch
@emdash-cms/plugin-embeds Patch

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

@pkg-pr-new

pkg-pr-new Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1827

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1827

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1827

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1827

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1827

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1827

emdash

npm i https://pkg.pr.new/emdash@1827

create-emdash

npm i https://pkg.pr.new/create-emdash@1827

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1827

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1827

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1827

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1827

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1827

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@1827

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@1827

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1827

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1827

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1827

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1827

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1827

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1827

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1827

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1827

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1827

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1827

commit: 7f3ee09

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions

Copy link
Copy Markdown
Contributor

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.

@github-actions github-actions Bot added stale and removed stale labels Jul 19, 2026
@ColoradoClimbr
ColoradoClimbr marked this pull request as ready for review July 20, 2026 03:57
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jul 20, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in MediaLibrary.tsx (local + provider grid/list) now carry loading="lazy".
  • The limit: 40 change in router.tsx is 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:

  1. Provider thumbnails are untested. The PR touches ProviderGridItem and ProviderListItem but 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.
  2. 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.
  3. MediaPickerModal uses 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");
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
});
});
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
// 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

ColoradoClimbr added a commit to Emdash-Bug-Testing/emdash that referenced this pull request Jul 20, 2026
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).
@github-actions github-actions Bot added review/needs-rereview New commits since the last review needs-rebase size/M cla: signed and removed review/needs-review No maintainer or bot review yet size/S labels Jul 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@ColoradoClimbr
ColoradoClimbr force-pushed the fix/media-library-lazy-thumbnails branch from e9af783 to a1a7db9 Compare July 22, 2026 04:20
ColoradoClimbr added a commit to Emdash-Bug-Testing/emdash that referenced this pull request Jul 22, 2026
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).
@github-actions github-actions Bot added query-count changed PR diff modifies query-count snapshot files area/ci area/docs size/XL and removed size/M labels Jul 22, 2026
@github-actions github-actions Bot removed the query-count changed PR diff modifies query-count snapshot files label Jul 22, 2026
ColoradoClimbr and others added 4 commits July 22, 2026 13:06
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>
…ary tests

mockResolvedValue set a persistent implementation that vi.clearAllMocks()
in beforeEach doesn't clear, so it leaked into a later test in the same
file and broke its empty-state assertion.
…y-thumbnails

# Conflicts:
#	packages/admin/tests/components/MediaLibrary.test.tsx
…y-thumbnails

# Conflicts:
#	packages/admin/src/components/MediaLibrary.tsx
#	packages/admin/src/router.tsx
#	packages/admin/tests/components/MediaLibrary.test.tsx
@emdashbot

emdashbot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Could not push formatting changes to this fork. The contributor may have "Allow edits by maintainers" disabled.

Please run the formatter locally:

pnpm format

ColoradoClimbr and others added 2 commits September 3, 2026 13:03
main added an emdash-media-transparency-grid class to the same <img>
elements this branch adds loading="lazy" to; kept both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2SdAvfCojeBZMwmyMdtNC
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2SdAvfCojeBZMwmyMdtNC
@ColoradoClimbr

Copy link
Copy Markdown
Contributor Author

Rebased onto main and resolved the merge conflict (main added an emdash-media-transparency-grid class to the same thumbnails this PR adds loading="lazy"" to — kept both). Also ran oxfmt/prettier` manually and pushed the fix, since maintainer-edit access on this fork is off and the bot couldn't auto-push its formatting patch. Should be green and mergeable now.

@ColoradoClimbr

Copy link
Copy Markdown
Contributor Author

Closing this one. Main has since added loading="lazy" to all four thumbnail imgs and swapped the 100-item infinite fetch for paginated 35/70/90, so there's nothing left for this PR to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants