Skip to content

fix(admin): show where local media is used - #2470

Open
khoinguyenpham04 wants to merge 9 commits into
feature/media-usage-production-activationfrom
feature/media-usage-used-in-ui
Open

fix(admin): show where local media is used#2470
khoinguyenpham04 wants to merge 9 commits into
feature/media-usage-production-activationfrom
feature/media-usage-used-in-ui

Conversation

@khoinguyenpham04

@khoinguyenpham04 khoinguyenpham04 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

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:

  • loads only when a local media detail panel is open and never requests usage for provider-managed assets;
  • groups references by content entry, links active entries, and labels trashed entries without linking them;
  • keeps known references visible while indexing is incomplete and explains the current coverage state with an accessible status icon;
  • supports bounded pagination with Load more and keeps long lists inside a 2.5-row scroll area;
  • protects unsaved media edits with the existing discard confirmation before same-tab navigation;
  • preserves responsive dialog behavior, keyboard access, RTL-safe layout, localization, and reduced-motion support;
  • does not use list-wide usage counts, claim complete coverage when it is unknown, or add automatic polling.

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

  • 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 (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: not applicable; this completes the admin surface for the existing Media Usage system

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5.6), with GPT-5.6 Terra adversarial review

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:

  • 58 focused admin browser tests passed;
  • admin typecheck and type-aware repository lint passed;
  • repository formatting passed;
  • the complete package workspace build passed;
  • Changesets validation passed;
  • the rebased PR5 patch has the same stable patch ID as the reviewed pre-restack branch.

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.

@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f376f58

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

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/admin Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Major
@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/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto 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

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache f376f58 Aug 21 2026, 04:20 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do f376f58 Aug 21 2026, 04:20 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground f20a473 Aug 15 2026, 11:47 PM

@khoinguyenpham04
khoinguyenpham04 marked this pull request as ready for review August 14, 2026 15:35
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 14, 2026
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Aug 14, 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 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:63

    MediaDetailPanel now calls useNavigate() (introduced in this PR), which requires a TanStack Router context. The focused test helper here only wraps the panel with QueryWrapper, 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, or useNavigate should 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 stubs useNavigate in this test file.)

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 14, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond overlap and removed review/needs-review No maintainer or bot review yet labels Aug 14, 2026
@github-actions

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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: f376f58

@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 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:63

    This re-review finds the same unresolved regression from the previous pass. MediaDetailPanel calls useNavigate() at component render time, which requires a TanStack Router context. The renderPanel helper here only wraps with QueryWrapper, so the existing tests will throw as soon as they render the panel.

    The new MediaDetailPanelNavigation.test.tsx already wires up a real router, so the simplest fix for this file is to add a module-level mock that stubs useNavigate:

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

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 15, 2026
@khoinguyenpham04

Copy link
Copy Markdown
Collaborator Author

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:63
    This re-review finds the same unresolved regression from the previous pass. MediaDetailPanel calls useNavigate() at component render time, which requires a TanStack Router context. The renderPanel helper here only wraps with QueryWrapper, so the existing tests will throw as soon as they render the panel.
    The new MediaDetailPanelNavigation.test.tsx already wires up a real router, so the simplest fix for this file is to add a module-level mock that stubs useNavigate:

    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.

@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 16, 2026
emdashbot[bot]
emdashbot Bot previously requested changes Aug 16, 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.

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:63

    This re-review finds the same unresolved regression from the previous pass. MediaDetailPanel calls useNavigate() at component render time, which requires a TanStack Router context. The renderPanel helper here only wraps with QueryWrapper, so the existing focused tests will throw as soon as they render the panel.

    MediaDetailPanelNavigation.test.tsx already wires up a real router, so the minimal fix for this file is to add a module-level mock that stubs useNavigate:

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

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 16, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Aug 16, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 17, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since needs-rebase and removed review/needs-rereview Author pushed changes since the last review labels Aug 17, 2026
…age-used-in-ui

# Conflicts:
#	packages/admin/src/lib/api/index.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs f376f58 Aug 21 2026, 04:19 AM

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