Skip to content

fix(plugin-forms): let editors star and mark submissions read, hide delete and export from them - #4756

Closed
marks-zyz wants to merge 2 commits into
emdash-cms:mainfrom
marks-zyz:fix/forms-editor-submission-triage
Closed

marks-zyz wants to merge 2 commits into
emdash-cms:mainfrom
marks-zyz:fix/forms-editor-submission-triage

Conversation

@marks-zyz

@marks-zyz marks-zyz commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Lets an Editor star a submission and change its status on the Submissions page of @emdash-cms/plugin-forms, and stops showing Editors the Delete and export controls they cannot use.

The page renders star, "Toggle status", Delete, CSV and JSON for every role. After #3373 an Editor can read, but submissions/update, submissions/delete and submissions/export declare no permission, so they fall back to plugins:manage (packages/core/src/plugins/http-route-dispatch.ts:55). Clicking the star or the eye as an Editor shows "You don't have permission to do this." This is the follow-up #3373 listed under "Left for a maintainer call".

  1. index.ts: submissions/update gets permission: "content:edit_any" (Editor tier). submissions/delete and submissions/export keep the plugins:manage default. The route-table comment now says which writes editors have.
  2. handlers/submissions.ts: submissions/list returns canManage (role >= ADMIN_ROLE, the same check that already redacts the webhook URL and the submitter details). The header comment is updated.
  3. admin.tsx: the Submissions page keeps canManage from the list response and renders Delete, CSV and JSON only when it is true. Star and "Toggle status" stay for everyone who can open the page.
  4. Tests: tests/submissions-editor-controls.test.tsx renders the page as an Editor and as an Admin against the real route permissions and handlers, and clicks every control (first commit, fails on main). tests/route-permissions.test.ts moves submissions/update to a new editor-triage group and still fails if a route is left unclassified. tests/submissions-list-can-manage.test.ts covers admin, editor and no-user.
  5. Changeset (patch).

Closes #4755

Decisions for a maintainer

  • Which permission: content:edit_any follows ai-search (entry/sync) and the plugin route docs ("a narrower RBAC permission when the operation belongs to an existing capability"). plugins:read also passes the new tests and is one line to swap; I did not use it because it names a read capability. content:edit_any exists at the emdash 0.11.0 peer floor.
  • Export stays Admin-only: exportHandler returns item.data including meta (IP, user agent, referer), which fix(plugin-forms): hide submitter IP, user agent and referer from editors #4294 hides from Editors in list and get.
  • Hide or allow: if you prefer every write to stay Admin-only, drop the permission line and also hide star and "Toggle status" behind canManage; the tests only need the expectation flipped.

Not in this PR

  • The Forms page has the same shape (New Form, Edit, Pause/Resume, Duplicate, Delete are shown to Editors and return 403). Left alone to keep the diff on the Submissions page; the same canManage approach would apply. Say if you want it here.
  • No new user-visible strings, so nothing to wrap for translation.
  • Not clicked through in a browser: the tests render the real page in jsdom against the real handlers and a mirror of the core permission rule. The original report was measured on a production site with an Editor account (plugin-forms 0.2.12).

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
  • Typecheck passes (tsc --noEmit -p packages/plugins/forms, exit 0; tsgo was not installed in my checkout)
  • Lint: oxlint --deny-warnings on packages/plugins/forms/src and tests is clean; the type-aware pass (--type-aware) did not run locally because the tsgolint binary was missing, so CI is the check for it
  • pnpm test passes (targeted: packages/plugins/forms)
  • oxfmt --check run on packages/plugins/forms/src, packages/plugins/forms/tests and the changeset: correct format
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are wrapped for translation: no new strings
  • I have added and reviewed the user-facing changeset
  • New features link to an approved Discussion: not a feature
  • I have included screenshots below if this PR changes the UI: text output below instead

AI-generated code disclosure

  • This PR includes AI-generated code. Model/tool: Claude Code (Claude Sonnet 5.5 and Claude Opus 5.5). Opened by the agent with the explicit authorization of the studio's maintainer, who reviews it after opening; the agent ran the failing test on main and the full plugin suite with the fix.

Screenshots / test output

main at b4a845b, first commit only (the new test, no fix):

vitest run tests/submissions-editor-controls.test.tsx
 × offers an Editor only controls the API accepts   [ 'CSV', 'JSON', 'star', 'Toggle status', 'Delete' ]
 × lets an Editor star a submission                 "You don't have permission to do this."
 × lets an Editor change the status                 "You don't have permission to do this."
 Tests  3 failed | 1 passed (4)

With the fix (packages/plugins/forms):

vitest run                      Test Files 27 passed (27), Tests 196 passed (196)   (main: 25 files, 188 tests)
tsc --noEmit -p .               exit 0
oxlint --deny-warnings src tests   exit 0
oxfmt --check src tests .changeset/forms-editor-submission-triage.md   correct format

The Admin case in the same test file asserts that star, "Toggle status", Delete, CSV and JSON are all still rendered for role 50 and that starring works.

…refuses

The Submissions page renders star, mark read, delete and export for every role, but submissions/update, submissions/delete and submissions/export fall back to plugins:manage. Renders the page as an Editor against the real route permissions and handlers, and fails for all five controls.
…elete and export from them

submissions/update declared no permission, so it fell back to plugins:manage while the Submissions page offered star and mark read to every role. It now takes content:edit_any (the editor tier, as ai-search entry/sync does). Delete and export stay plugins:manage; submissions/list returns canManage so the page stops showing editors those two controls. Export stays admin-only because its JSON carries the IP, user agent and referer that list and get already hide from editors.
@changeset-bot

changeset-bot Bot commented Nov 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 148d57f

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

This PR includes changesets to release 1 package
Name Type
@emdash-cms/plugin-forms 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

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

Letting Editors triage submissions while delete and export stay Admin-only is a sound, scoped approach. The page uses canManage from submissions/list to hide the Admin-only controls. Both reviewers found one blocking problem: opening submissions/update to Editors lets them read the submitter metadata (IP, user agent, referer) that the list and get routes redact. That should be fixed before merge. The second reviewer also raised a test fixture type error and two repository-convention issues (a literal permission assertion and redundant comments). The first reviewer did not mention these.


Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.


Findings

  • [needs fixing] packages/plugins/forms/src/handlers/submissions.ts:94

    Now that submissions/update accepts content:edit_any (Editor tier), this line returns the full stored Submission to Editors, including meta.ip, meta.userAgent and meta.referer. submissions/list and submissions/get hide those from Editors through visibleTo (#4294).

    submissionUpdateSchema makes status, starred and notes optional. An Editor can therefore post { id } with no changes and read the submitter details for any submission. That undoes the redaction this PR claims to keep. The admin page ignores the response body, so redacting it breaks nothing:

    	return { id: input.id, ...visibleTo(ctx, updated) };
    

    Please add a test that calls the update handler as an Editor (role 40) and asserts the result has no ip, userAgent or referer. Both reviewers flagged this issue. The second one anchored it on the route permission at index.ts:177 and also asked for Admin update-response coverage.

  • [needs fixing] packages/plugins/forms/tests/submissions-editor-controls.test.tsx:144

    This fixture is declared to return Submission, but its meta omits the required SubmissionMeta.referer field. Vitest transpilation does not catch this, and the package typecheck excludes tests. Add the missing field:

    		meta: { ip: "203.0.113.7", userAgent: "Mozilla/5.0", referer: null, country: "DE" },
    
  • [needs fixing] packages/plugins/forms/tests/route-permissions.test.ts:44-46

    This test asserts the exact permission literal added to the route table instead of testing behavior (an Editor can triage, a lower role is refused). AGENTS.md prohibits this config-pin pattern. Remove it or replace it with authorization coverage through the real dispatcher, including a denied lower-role request.

  • [needs fixing] packages/plugins/forms/src/handlers/submissions.ts:59

    This new comment restates the field's UI use and the permission policy, which the adjacent canManage expression and conditional rendering already show. The same redundant comment is added above the state declaration in admin.tsx (~line 1145). AGENTS.md prohibits comments that narrate the change or restate clear code. Remove both.

@pkg-pr-new

pkg-pr-new Bot commented Nov 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-test

npm i https://pkg.pr.new/@emdash-cms/plugin-test@4756

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-loader

npm i https://pkg.pr.new/@emdash-cms/registry-loader@4756

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/@emdash-cms/registry-moderation@4756

@emdash-cms/registry-verification

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

upgrade-emdash

npm i https://pkg.pr.new/upgrade-emdash@4756

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-ai-search

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-search@4756

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 148d57f

@marks-zyz

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #4762, which merged as afac393 and fixes #4755 the other way round: it hides the star, status, Delete, CSV and JSON controls from Editors (the list route now returns canManage), while this PR let Editors star and mark submissions read and hid only Delete and export. Editors starring and changing status is therefore not carried over.

Checked this PR's test file submissions-editor-controls.test.tsx against afac393: the Admin case passes. The two cases that expect an Editor to star and change status fail, because the Editor page has no such buttons. "Offers an Editor only controls the API accepts" also fails as written, because it asserts that at least one button exists and the Editor page now has none; with that assertion removed it passes and the Editor page has no buttons and no links.

Not released yet: the latest @emdash-cms/plugin-forms on npm is 0.2.12, and #4762 is listed under 0.2.13 in the open release PR #4506.

Tested by an AI agent on behalf of the maintainer.

@marks-zyz marks-zyz closed this Nov 8, 2026
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.

plugin-forms: the Submissions page offers Editors star, status, delete and export, but those routes require plugins:manage

1 participant