fix(admin): show where local media is used - #2470
Conversation
🦋 Changeset detectedLatest commit: f376f58 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 1,194 lines across 9 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 |
emdash-demo-cache | f376f58 | Aug 21 2026, 04:20 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | f376f58 | Aug 21 2026, 04:20 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | f20a473 | Aug 15 2026, 11:47 PM |
There was a problem hiding this comment.
This PR is the right change for the right problem: it exposes the existing coverage-aware media-usage backend in the admin UI so editors can see where a local media asset is used and open those entries, without adding provider-asset usage, polling, or list-wide counts. The implementation follows EmDash conventions: all user-facing strings are Lingui-wrapped, layout uses RTL-safe logical Tailwind classes, the API client handles the { success, data } envelope and maps 401/403 to a private MediaUsageAccessDeniedError, and the changeset correctly bumps the fixed package group.
I read the full diff, the new and changed files, and traced the media-usage server implementation to confirm the client assumptions (paging, coverage aggregation, and grouping by content entry). The code is careful about disabled navigation during saves, preserves cached references on refresh errors, and keeps the section lazy so provider assets never trigger usage queries.
One concrete regression: MediaDetailPanel now calls useNavigate() from @tanstack/react-router, but the existing focused test helper in MediaDetailPanel.test.tsx only wraps with QueryWrapper, not with <RouterProvider>. Rendering the component there will throw because TanStack Router hooks require a router context. The new MediaDetailPanelNavigation.test.tsx correctly supplies one, so the fix is to either wrap the existing helper or mock useNavigate in that file.
Findings
-
[needs fixing]
packages/admin/tests/components/MediaDetailPanel.test.tsx:63MediaDetailPanelnow callsuseNavigate()(introduced in this PR), which requires a TanStack Router context. The focused test helper here only wraps the panel withQueryWrapper, so the existing tests will throw at render when the hook cannot find a<RouterProvider>. The new navigation test file already wires up a router; this helper needs the same treatment, oruseNavigateshould be mocked in this file.function renderPanel(props: Partial<React.ComponentProps<typeof MediaDetailPanel>> = {}) { const defaultProps: React.ComponentProps<typeof MediaDetailPanel> = { open: true, item: makeImageItem(), onClose: vi.fn(), onDeleted: vi.fn(), ...props, }; return render( <QueryWrapper> <RouterProvider router={createTestRouter()}> <MediaDetailPanel {...defaultProps} /> </RouterProvider> </QueryWrapper>, ); }(Alternatively, add a module-level
vi.mock("@tanstack/react-router", …)that stubsuseNavigatein this test file.)
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. |
@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: |
There was a problem hiding this comment.
This is the right change for the right problem: it surfaces the existing coverage-aware media-usage backend in the admin UI so editors can see where a local media asset is used and open those entries, without adding provider-asset usage, polling, or list-wide counts. The implementation follows EmDash conventions: all user-facing strings are Lingui-wrapped, layout uses RTL-safe logical Tailwind classes, the API client handles the { success, data } envelope and maps 401/403 to a private MediaUsageAccessDeniedError, and the changeset correctly bumps the fixed package group.
In this re-review I re-read the full diff, the new/changed files, and the prior finding. The only remaining blocker from the previous review was not addressed. MediaDetailPanel now unconditionally calls useNavigate() from @tanstack/react-router, but the existing focused test helper in MediaDetailPanel.test.tsx still only wraps the panel with QueryWrapper, so every test using that helper will throw at render because TanStack Router hooks require a <RouterProvider> context. The new MediaDetailPanelNavigation.test.tsx already supplies one, so the minimal fix is to stub useNavigate in MediaDetailPanel.test.tsx (or wrap the helper with a router).
Everything else looks solid: MediaUsedIn is lazy and gated on both open and !isProviderAsset, coverage is aggregated conservatively, cached references are kept visible on refresh errors, keyboard/modifier/aux clicks are guarded while saving, and the new tests cover access denial, pagination, coverage states, RTL direction, and navigation discard flow.
Findings
-
[needs fixing]
packages/admin/tests/components/MediaDetailPanel.test.tsx:63This re-review finds the same unresolved regression from the previous pass.
MediaDetailPanelcallsuseNavigate()at component render time, which requires a TanStack Router context. TherenderPanelhelper here only wraps withQueryWrapper, so the existing tests will throw as soon as they render the panel.The new
MediaDetailPanelNavigation.test.tsxalready wires up a real router, so the simplest fix for this file is to add a module-level mock that stubsuseNavigate:vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal<typeof import("@tanstack/react-router")>(); return { ...actual, useNavigate: () => vi.fn(), }; });(Alternatively, wrap the helper with
<RouterProvider>the same way the navigation test does.)
im declining this because the installed TanStack Router does not throw when useNavigate renders, the exact 28-test file and full 1,453-test admin suite pass, and navigation behavior is already covered separately with a real RouterProvider. |
There was a problem hiding this comment.
Re-review of the same diff. The approach is still sound: this surfaces the coverage-aware media-usage backend in the admin UI so editors can see where a local media asset is used and open those entries, without adding provider-asset usage, polling, or list-wide counts. The implementation still follows EmDash conventions — Lingui-wrapped strings, RTL-safe Tailwind, the { success, data } envelope, conservative coverage aggregation, and the changeset is in place.
However, the one remaining blocker from the previous review is not addressed. MediaDetailPanel now calls useNavigate() from @tanstack/react-router, but MediaDetailPanel.test.tsx still renders the component through a helper that only wraps with QueryWrapper. That means every test using renderPanel will throw because TanStack Router hooks require a <RouterProvider> context. The separate MediaDetailPanelNavigation.test.tsx already supplies a router, so the focused tests just need a module-level mock for useNavigate.
Once that mock is added, this PR looks ready to merge. I'm not flagging other issues: RTL direction handling, coverage states, pagination, access-denial behavior, and the navigation discard flow are all covered by tests; the manifest query pattern here matches existing admin usage; and the API client correctly maps 401/403 to the private MediaUsageAccessDeniedError without leaking server details.
Findings
-
[needs fixing]
packages/admin/tests/components/MediaDetailPanel.test.tsx:63This re-review finds the same unresolved regression from the previous pass.
MediaDetailPanelcallsuseNavigate()at component render time, which requires a TanStack Router context. TherenderPanelhelper here only wraps withQueryWrapper, so the existing focused tests will throw as soon as they render the panel.MediaDetailPanelNavigation.test.tsxalready wires up a real router, so the minimal fix for this file is to add a module-level mock that stubsuseNavigate:vi.mock("@tanstack/react-router", async (importOriginal) => { const actual = await importOriginal<typeof import("@tanstack/react-router")>(); return { ...actual, useNavigate: () => vi.fn(), }; });(Alternatively, wrap the helper with
<RouterProvider>the same way the navigation test does.)
…age-used-in-ui # Conflicts: # packages/admin/src/lib/api/index.ts
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | f376f58 | Aug 21 2026, 04:19 AM |
What does this PR do?
Adds a Used in section to local media details so editors can see which content entries reference an image or file and open those entries directly.
The section:
The presentation follows the useful frontend patterns from #2225 while using the coverage-aware Media Usage APIs delivered by the current stack.
This PR is stacked directly on #2445 and should be reviewed relative to
feature/media-usage-production-activation.Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output
This is a visual change. The branch preview from CI can be used to inspect the final responsive panel.
Verified locally after restacking on #2445:
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
feature/media-usage-used-in-ui. Updated automatically when the playground redeploys.