Repository navigation
fix(admin): name the field by its label when a save fails validation - #3205
Conversation
A save the content validator rejected reached the editor as one joined string that named each field by its slug and used the validator's own wording, e.g. "excerpt: Too big: expected string to have <=160 characters". The validator built those failures per field and then joined them, so the admin had nothing structured to word differently. The validator now also returns the failures in error.details.issues, one per field, with a code and the bound that failed; error.message is unchanged. The admin turns them into a translatable sentence per field that uses the label the editor shows, in the toasts for a save, an autosave, a new entry and a new translation. An error without these issues keeps the server message.
🦋 Changeset detectedLatest commit: ac93288 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 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 728 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. |
@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-test
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-loader
@emdash-cms/registry-moderation
@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 fix for the right problem. The previous validation error message joined every failing field into one opaque string, so the admin had no per-field context to produce a human-readable toast. The change keeps the joined server message intact for backward compatibility but surfaces structured per-field issues in error.details.issues, then maps those issues to localized sentences using the editor-visible field labels. That fits cleanly into EmDash's API-envelope shape, Lingui-based admin, and the existing VALIDATION_ERROR / ApiResponseError flow.
I checked:
- Approach / architecture: The content validator now returns
details.issues; the admin's newdescribeContentValidationErrorturns each issue into a translatable sentence and falls back to the server message for non-validation errors. This is additive and backward-compatible. - Core validation changes:
validateContentDatais called fromhandleContentCreateandhandleContentUpdateinpackages/core/src/emdash-runtime.tswithpartial: false/partial: truerespectively. The new issue objects are sanitized (no rawissue.inputis returned) and SQL identifiers remain parameterized / validated. - Admin error wiring:
ContentNewPage,ContentEditPagesave,ContentEditPageautosave, and translation creation all use the new helper. Other content mutations (publish, unpublish, schedule, discard, delete) do not carry user-edited data, so they are correctly out of scope. - Localization: All new user-facing strings go through Lingui macros (
msg,plural) and the globali18n._()call is already a repo-wide pattern. Nomessages.pofiles are touched. - RTL / Tailwind / comments: No new layout classes; no new logged-out queries; no new queries against content tables. Comments added are explanatory JSDoc and non-obvious "why" notes, not reviewer-facing narrative.
- Tests: Core integration tests pin every issue code the admin words; admin unit tests cover sentence construction, label fallback, deduplication of required+type errors, and the fallback to server messages; router tests and a Playwright autosave test exercise the full save/autosave paths.
- Changeset: The changeset names the user-facing behavior, the affected packages are correct (
emdashand@emdash-cms/admin), and the bump type ispatch.
The code is clean and I found no blocking issues.
…d-labels # Conflicts: # packages/admin/src/components/ContentEditor.tsx
What does this PR do?
Names each rejected field by its editor label and says what it needs, so
excerpt: Too big: expected string to have <=160 charactersbecomes "Summary can have at most 160 characters."The content validator joined per-field failures into one message, so #1838's per-field formatting never ran for content saves. It now returns them in
error.details.issueswith path, code and any failed bound;error.messageis unchanged. The admin's save toasts turn each issue into a translatable sentence; other errors keep the server message. LikeSAVE_REJECTED's details, the issues serve the admin, so the changeset and OpenAPI leave them out; the core test pins each code the admin words, so a Zod upgrade that renames one fails there.Closes #2426
Part of #2417 (item 2, requested here).
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
A Summary field (
minLength: 20) and a Reading time field (min: 1,max: 60) rejected by one autosave.The same editor in Arabic; the new sentences stay English until the catalog is translated.
main: three of the four new core cases (the fourth pins the unchangederror.message) and both new cases inrouter.test.tsx;content-validation-errors.test.tscovers the wording.pnpm exec playwright test autosave→ 3 passed, one of them new;pnpm typecheckclean;pnpm lint:json→ 0 diagnosticspnpm --filter @emdash-cms/admin test→ 2367 passed;pnpm --filter emdash test→ 6897 passed, 1 failed:cron-schedule.test.tsfails in a Europe/Berlin shell and passes withTZ=UTC, outside this diff