feat(media): add numbered library pagination - #2582
Conversation
# Conflicts: # packages/admin/src/components/MediaLibrary.tsx # packages/admin/src/router.tsx # packages/admin/tests/components/MediaLibrary.test.tsx
🦋 Changeset detectedLatest commit: 5e0df07 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 |
Scope checkThis PR changes 912 lines across 18 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 007ebb7 | Aug 20 2026, 02:15 PM |
@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: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 5e0df07 | Aug 20 2026, 03:35 PM |
🚀 Deploying Preview to Cloudflare 🚀Preview URL: https://feat-media-pagination-ui.try.emdashcms.com, https://feat-media-pagination-ui-emdash-playground.emdash-cms.workers.dev (commit 5e0df07)This URL reflects your latest Preview deploymentPreview Deployments by commit
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | 5e0df07 | Aug 20 2026, 03:35 PM |
There was a problem hiding this comment.
This is the right follow-up to Discussion #897: it adds an opt-in numbered-page mode to the media list API and a Kumo-based paginator for the local Media Library, while leaving the existing cursor path untouched for media pickers, providers, CLI, MCP, and plugins. I checked the changed files end-to-end (admin component, router state, API handler/schema/route, repository findPage, client types, and tests). SQL is parameterized, authorization still gates the route, strings go through Lingui, RTL mirroring is present, and the admin-only route does not add logged-out queries. The implementation handles page recovery when deletion/filtering empties the current page, and the totalCount is only computed in page mode.
The only blocking documentation issue is the changeset: this is a new feature that also removes hasMore/onLoadMore from the exported MediaLibraryProps, so it should be a minor bump and the entry needs more detail about the public surface and compatibility.
There was a problem hiding this comment.
Re-review of #2582: the prior blocking documentation issue is resolved. The changeset now correctly lists both affected packages ("emdash" and "@emdash-cms/admin") as minor, and it explains the user-facing capability, the opt-in page API shape, and that existing hasMore/onLoadMore props continue to work when pagination is omitted.
I re-read the full diff and traced the change end-to-end:
- Approach: This is the right follow-up to Discussion #897. It adds an opt-in numbered-page mode to the media list API and a Kumo
Paginationcontrol for the local Media Library, while leaving the existing cursor path untouched for media pickers, providers, CLI, MCP, and plugins. - API & handler:
mediaListQueryrejectscursor+pagetogether and validatespageto safe integers.handleMediaListcapslimitat 100, validates the offset, and dispatches torepo.findPage(). The route passestotalCountthrough only in page mode (cursor mode omits it). - Repository:
MediaRepository.findPage()reuses the newapplyListFilters()helper so MIME/search/status filters apply consistently to both rows and count; SQL is parameterized via Kysely (escapeLike,sqltagged templates, no raw interpolation). - Admin UI:
MediaLibraryaccepts an optionalpaginationprop and still supports legacyhasMore/onLoadMore. Focus management after page/page-size changes is implemented with refs and an effect gated onpagination?.isPending. RTL mirroring viartl:[&_svg]:-scale-x-100is present. - Router/page state:
MediaPageuseskeepPreviousDatato keep the current grid rendered during fetches, recovers from an invalidated higher page, and resets page to 1 on new searches/filters/page-size changes. - Tests: New repository, handler, schema, client, component, and router tests cover page totals, validation, empty-page recovery, focus restoration, and stable loading states. Existing cursor/picker flows remain covered.
- Auth / logged-out queries: The media route still gates on
media:read/media:upload; no new logged-out work is introduced. - Lingui / i18n: All new user-facing strings and ARIA labels are wrapped for translation.
- Changeset: Now meets the
.changeset/README.mdstandard — it leads with the released behavior, names the public surface, and includes compatibility guidance.
No further blocking issues, regressions, or convention violations remain.
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. |
What does this PR do?
Adds numbered pagination to the local Media Library using Kumo's compound Pagination component. The new opt-in
pageAPI mode returns an exacttotalCount, while existing cursor requests remain unchanged and keep their one-query path for media pickers, providers, CLI, MCP, and plugins.The admin now provides 35/70/90 page sizes, stable loading without replacing the current grid, preserved scroll and focus, responsive wrapping, and RTL-safe controls. It also recovers cleanly when deletion or filtering makes the current page invalid, including when the result count drops to zero.
This is a follow-up to the accepted media-pagination Discussion #897 and its original cursor-based fix in #996.
Related to #897
Type of change
Checklist
emdashand@emdash-cms/adminpackagesmessages.pochanges are included in the PR diff.AI-generated code disclosure
Screenshots / test output
emdash,@emdash-cms/admin, anddemos/simplepassed.totalCount: 127; cursor mode returnednextCursorwithouttotalCount.PostgreSQL was not configured locally, so dialect tests ran on SQLite. Admin Vitest reports the repository's existing mixed
vitest@4.1.5/@vitest/browser@4.1.10warning; all tests pass.