Skip to content

feat(admin): allow trusted plugins to add content list columns - #2195

Open
logelog wants to merge 7 commits into
emdash-cms:mainfrom
logelog:feat/plugin-content-list-columns-v2
Open

feat(admin): allow trusted plugins to add content list columns#2195
logelog wants to merge 7 commits into
emdash-cms:mainfrom
logelog:feat/plugin-content-list-columns-v2

Conversation

@logelog

@logelog logelog commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a focused, display-only content-list column extension for trusted React plugins.

Plugins can export typed contentListColumns definitions for computed metadata such as review statuses, moderation outcomes, or publication readiness. EmDash retains ownership of the table, pagination, row actions, loading states, and empty states while passing each cell the current content item, collection, and locale.

The resolver:

  • filters disabled or stale plugins
  • supports collection and minimum-role visibility
  • orders contributions deterministically
  • validates malformed and duplicate definitions
  • isolates failing predicates, headers, and cells so the content list remains usable
  • omits plugin columns from Trash

Sorting, filtering, and search remain outside this display-only contract because they require server-backed contracts. Search is covered by #2191, indexed sorting by #2212, and indexed filtering by #2213.

#2194 has merged and supplies the shared content-list layout. This branch is now based on main and contains only the trusted-plugin extension commits.

Approved Discussion: #2048

Wider content-list context: #2397

Closes: N/A

Type of change

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

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5, GPT-5.6

Screenshots / test output

Local validation after rebasing:

  • focused admin browser tests: 2 files, 63 tests passed
  • admin production build
  • core and admin typechecks
  • full type-aware lint
  • full repository format check
  • git diff --check

No screenshot is included because the extension renders only when a trusted plugin contributes a column.

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5760ef3

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 586 lines across 10 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.

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jul 22, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 5760ef3

@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 a focused, well-scoped addition to the trusted-plugin admin surface. It solves the right problem (display-only content-list columns contributed by trusted React plugins) in the right way: EmDash keeps ownership of the table while plugins provide header/cell components;filtering, sorting, and search are deliberately left out because they need server-backed semantics. The change follows the same trusted-plugin model already used for pages, widgets, and field widgets, and it is backed by an approved Discussion (#2048).

I checked the new resolver, the boundary component, the ContentList integration, the router wiring, the test coverage, and the changeset. The logic is sound: disabled/stale plugins are omitted, role and collection filters work, malformed/duplicate definitions are isolated, and error boundaries keep a broken column from crashing the list. No logic bugs, security issues, regressions, or logged-out query-count impacts were found.

There are three AGENTS.md import-extension violations on changed/new lines: two new internal imports omit the required .js extension. Fixing those is the only blocker.

import { Trans } from "@lingui/react/macro";
import * as React from "react";

import type { AdminManifest, ContentItem } from "./api";

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] Internal ESM imports must use a .js extension per AGENTS.md. This new file imports from ./api without it.

Suggested change
import type { AdminManifest, ContentItem } from "./api";
import type { AdminManifest, ContentItem } from "./api.js";

ContentDateField,
ContentItem,
TrashedContentItem,
} from "../lib/api";

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] This changed import from ../lib/api is missing the required .js extension for internal ESM imports.

Suggested change
} from "../lib/api";
} from "../lib/api.js";

Comment thread packages/admin/src/index.ts Outdated
ContentListColumnHeaderContext,
ContentListColumnCellContext,
ContentListColumnExtension,
} from "./lib/content-list-columns";

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 new re-export from ./lib/content-list-columns is an internal ESM import and needs the .js extension.

Suggested change
} from "./lib/content-list-columns";
} from "./lib/content-list-columns.js";

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

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet review/awaiting-author Reviewed; waiting on the author to respond labels Jul 22, 2026
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from ee1f044 to 0a83f6d Compare July 22, 2026 22:57
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 0a83f6d to 8825b20 Compare July 24, 2026 19:26
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 8825b20 to ffe1365 Compare August 2, 2026 23:36
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from ffe1365 to 139939b Compare August 3, 2026 12:20
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 36d5c8c to bacadcf Compare August 10, 2026 17:30
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 10, 2026
@logelog logelog closed this Aug 10, 2026
@logelog logelog reopened this Aug 10, 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 trusted-plugin content-list column extension. The approach is sound: EmDash keeps full ownership of the table, pagination, row actions, loading/empty states, and sort/filter contracts while letting trusted React plugins and collection-configured scalar fields contribute display-only columns. This fits EmDash’s existing manifest-driven plugin architecture without leaking host responsibilities to plugins.

The only prior finding—the extensionless barrel-file export in packages/admin/src/components/index.ts—is now fixed (./ContentList.js).

I re-read the full diff end-to-end:

  • ContentList correctly integrates collection-configured columns between title/status and plugin-contributed columns between date/actions, updates colSpan everywhere, and leaves sort/filter/search untouched.
  • resolveContentListColumns deterministically orders contributions, filters disabled/stale plugins and unauthorized roles, validates malformed definitions, deduplicates per-plugin IDs, and isolates throwing collection predicates.
  • ContentListColumnBoundary contains header and cell render failures so one broken plugin cannot unmount the list, and it only renders in the active-content table (not Trash).
  • User-facing strings are Lingui-wrapped; alignment uses RTL-safe logical Tailwind (text-start/text-end).
  • Core manifest build, schema registry persistence, migrations, seed/validate/export, and API schemas all correctly cap list columns at four, restrict them to supported field types, and preserve them round-trip.
  • New tests cover configured and plugin columns, formatting, colSpan alignment, error isolation, Trash omission, and the resolver’s filtering/ordering/validation behavior.

I found no logic bugs, security issues, locale-filter misses, broken API-envelope contracts, AGENTS.md convention violations, or logged-out query-count regressions on this pass.

One non-code item remains: per the PR description, the linked Discussion is still awaiting maintainer approval; that is a governance gate outside the code review itself. LGTM.

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 10, 2026
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from bacadcf to 760dcb3 Compare August 11, 2026 02:48
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 11, 2026
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 760dcb3 to 9a3ba0b Compare August 11, 2026 13:56
@ascorbic

Copy link
Copy Markdown
Collaborator

@logelog If you resolve the conflicts we can get this in

@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 9a3ba0b to 7652b23 Compare August 11, 2026 16:19
@logelog

logelog commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@logelog If you resolve the conflicts we can get this in

Thanks a lot! I’ve rebased the branch onto the current main and resolved the conflicts. All checks are green now.

@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 7652b23 to 86099f1 Compare August 11, 2026 21:08

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Plugin column labels must remain localizable, and the public cell contract must document batching to prevent per-row requests.

return (
<th
scope="col"
aria-label={extension.label}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This renders the plugin label directly, so an Arabic admin still gets an English column name (and the same untranslated accessible name), even when the plugin catalog contains a translation. Resolve the label through the active Lingui instance, as plugin page labels are, and use that value for the aria label and both fallbacks.


## Content-list columns

Trusted React plugins can add read-only columns to active content collection lists. EmDash keeps ownership of the table, pagination, row actions, and loading and empty states; the plugin supplies only the header metadata and cell content.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The cell contract needs the batching constraint from the approved design. A cell that fetches metadata by item id will mount once per row and turn a 20-row page into 20 requests. Document and demonstrate one shared query key or batched request keyed by visibleItems.

@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 12, 2026
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from 86099f1 to acc1ed3 Compare August 12, 2026 12:51
@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 12, 2026
@logelog
logelog force-pushed the feat/plugin-content-list-columns-v2 branch from d1df443 to 5760ef3 Compare August 12, 2026 17:29
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.

3 participants