Skip to content

Add admin panels to the Payload plugin - #39

Merged
mgilardi merged 1 commit into
mainfrom
plugin-admin-panels
Nov 12, 2026
Merged

mgilardi merged 1 commit into
mainfrom
plugin-admin-panels

Conversation

@mgilardi

Copy link
Copy Markdown
Collaborator

Part 5 of the Payload plugin: a panel at the top of each collection's edit page, so a manager no longer publishes by editing a status field, watches an import as raw JSON, or decides on a proposal through the API.

What it adds

  • Snapshot panel. Status in words, what the snapshot holds, its sources and licenses, and for one made from a proposal the two sealed person ids. Publish and Withdraw for a manager.
  • View panel. A drawing of the saved view (first 200 nodes) with the same tree as a keyboard outline, and what readers are told. Publish waits, with the reason, until the form is saved and the snapshot is published.
  • Import panel. The steps of a run, warnings and diagnostics. It looks again every 3 seconds while a job runs, and offers to run a queued, failed or interrupted job.
  • Proposal panel. What the proposal would change, then Accept (naming the new version) or Decline.
  • One endpoint, GET /api/<snapshots>/:id/summary: counts and capped lists, never names or trees, with the read API's 404.
  • admin.panels (default on) and the @captiveorigins/payload-plugin-onetaxonomy/client entry. Hosts run payload generate:importmap after adding the plugin.

How it stays safe

  • The panels have no powers of their own. They call the host's site from the browser as the signed-in person, so the host's access callbacks decide every answer. A button shown by mistake still gets the site's 403 or 404, and a refusal reads "Nothing was changed." with the reason.
  • Every button asks first, in the page, and says what can't be undone. Before accepting, the panel lists what gets sealed for good and asks the manager to check it for personal details.
  • A name beside a sealed id is looked up when the page loads, for signed-in requesters only, and is never stored. A visitor who may read a snapshot gets the ids alone.

Found on the way

Payload's own admin makes two requests to other sites on these pages: its JSON field loads Monaco from cdn.jsdelivr.net, and its default avatar asks gravatar.com with a hash of the signed-in person's email address. The README says so and how to turn the second off; the example site now does.

Tested

  • 94 new tests: 74 render the panels in jsdom with Payload's hooks and fetch replaced by stand-ins, 11 read the installed @payloadcms/ui stylesheets and check contrast in light and dark, 9 run the summary against a real Payload with an in-memory MongoDB. 621 unit tests pass in all.
  • 8 new checks over HTTP in the example site (55 in all), on the built site.
  • 19 deliberate breaks, 18 caught. docs/test-audit.md says which wasn't and why.
  • The snapshot, view and proposal panels were clicked through once in headless Chrome on the example site, in light and dark.

Not tested

  • That browser run is not in CI.
  • The import panel has not run in a real admin (the example site has no imports).
  • No screen reader, Safari, Firefox, phone or custom Payload theme.
  • Postgres and SQLite, as before.

Still not on npm. See ADR 0012, point 16.

🤖 Generated with Claude Code

Part 5 of the plugin. Until now a manager published a snapshot by editing
a status field, watched an import as raw JSON, and decided on a proposal
through the API. Each of the four collections now opens with a panel on
its edit page: what the record holds, its status in words, and the next
step, asked for in the page before anything is sent.

The panels have no powers of their own. They run in the browser and call
the host's site as the signed-in person, so the host's access callbacks
decide every answer, and a refusal reads "Nothing was changed." with the
site's reason. They are a `ui` field on Payload's own edit page, not a
custom view, so Payload's save, permissions and versions stay as they are.

One new endpoint, GET /api/<snapshots>/:id/summary: counts and capped
lists, never names or trees, with the read API's 404. For a snapshot made
from a proposal it gives the name the host shows for each sealed person
id, to signed-in requesters only, looked up per request and never stored.

Before accepting, the panel lists what gets sealed for good and asks the
manager to check it for personal details, because the plugin doesn't
screen free text.

Colours come from Payload's theme variables. A test reads the installed
@payloadcms/ui stylesheets and checks contrast in light and dark, and a
status is never colour alone.

The example site shows the panels to its curator, and turns off Payload's
default avatar, which asked gravatar.com with a hash of the signed-in
address. The README says so, and that Payload's JSON field loads its
editor from cdn.jsdelivr.net.

Tested: 94 new tests (74 in jsdom with Payload's hooks and fetch replaced
by stand-ins, 11 on the stylesheet, 9 on the summary against a real
Payload with an in-memory MongoDB), 8 new checks over HTTP in the example
site, and 19 deliberate breaks of which 18 were caught; the audit says
which wasn't and why. The snapshot, view and proposal panels were clicked
through once in headless Chrome on the example site, in light and dark.

Not tested: that browser run is not in CI. The import panel has not run
in a real admin. No screen reader, Safari, Firefox, phone or custom
Payload theme. Postgres and SQLite, as before.

Co-Authored-By: Claude <noreply@anthropic.com>
@mgilardi
mgilardi merged commit 374db14 into main Nov 12, 2026
1 check passed
@mgilardi
mgilardi deleted the plugin-admin-panels branch November 12, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant