Skip to content

fix: failed validation clears unsaved content#377

Open
adrianboros wants to merge 6 commits into
stagingfrom
adrian/intorg-796-save-with-error-resets-state
Open

fix: failed validation clears unsaved content#377
adrianboros wants to merge 6 commits into
stagingfrom
adrian/intorg-796-save-with-error-resets-state

Conversation

@adrianboros

Copy link
Copy Markdown
Contributor

Summary

In the Strapi admin, saving a page with a validation error would show the error but also wipe the editor's unsaved changes back to the last-saved DB state.

Root causes found (three separate, but related bugs)

  1. Client-side cache bug in @strapi/content-managerupdateDocument's RTK Query mutation optimistically writes edited data into the cache, then rolls it back (patchResult.undo()) on any error, and both updateDocument/publishDocument invalidated their cache tags even on failure. The admin's <Form> re-syncs to that reverted cache, wiping in-progress edits.
  2. Custom validators ran too late — my first fix moved validation into beforeCreate/beforeUpdate lifecycle hooks, but those see component fields (primaryCta, hero, etc.) already resolved to {id, __pivot} DB references, not the raw {text, link} shape — causing false-positive rejections on valid saves. Moved validation to Koa middleware reading the raw request body instead (matching the pre-existing, correct validateNoNestedJsx pattern).
  3. Missing field-level error data — validators didn't attach details.errors[].path, and even after fixing that, the middleware's response body construction dropped .details entirely, so the admin could only show a generic toast, never highlight the specific field.

Fixes added:

  • patches/@strapi__content-manager@5.41.1.patch (pnpm native patch, scoped in cms/pnpm-workspace.yaml — cms has its own standalone lockfile separate from the root workspace, which took a few tries to discover): stops the optimistic-update rollback and skips cache invalidation on error for updateDocument/publishDocument.
  • cms/src/utils/contentValidation.ts: every validator (validateGrantPagePrimaryCta, validateGrantPageFaqSection, validateCtaStrip, validateProfileCta, validateHeroFields, validateBlogFields, validateNavigationLabels) now collects all failing fields per save (not just the first) via combineFieldErrors, and mergeValidationErrors combines multiple validators' results for one content type.
  • cms/src/index.ts: registerBodyValidationMiddleware helper validates the raw request body pre-write and now correctly forwards details. Added missing validation entirely for grant-overview-page and profile-page (had none), and added ctaStrip validation to grant-page/grant-overview-page (a required component whose required sub-fields were never enforced on update).
  • Reverted the now-wrong beforeCreate/beforeUpdate lifecycle validation additions in pageLifecycle.ts, navigationLifecycle.ts, blogLifecycle.ts.

Related Issue

Fixes INTORG-796

Manual Test

Test validation on any page see if the issue is fixed and we don't lose content.
Check valid updates, check mdx file sync logic both ways.

Checks

  • pnpm run format
  • pnpm run lint

PR Checklist

  • PR title follows Conventional Commits (e.g. feat: ..., fix: ...)
  • Linked issue included
  • Scope is focused (target ~10-20 files when possible)
  • Screenshots for UI changes (if applicable)
Screenshot 2026-07-09 at 23 39 57 Screenshot 2026-07-09 at 23 40 09 Screenshot 2026-07-09 at 23 40 26 Screenshot 2026-07-09 at 23 40 44

Copilot AI review requested due to automatic review settings July 9, 2026 21:29
@netlify

netlify Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy Preview for interledger-org-v5 ready!

Name Link
🔨 Latest commit 39dc178
🔍 Latest deploy log https://app.netlify.com/projects/interledger-org-v5/deploys/6a5012d0da674a00088ad429
😎 Deploy Preview https://deploy-preview-377--interledger-org-v5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI 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.

Pull request overview

This PR fixes a Strapi admin UX issue where a failed save (validation error) would revert the editor’s unsaved changes back to the last-saved state. It does this by patching Strapi’s content-manager RTK Query caching behavior on failed mutations and by moving/expanding server-side request-body validation to return field-level errors that the admin can highlight.

Changes:

  • Patch @strapi/content-manager to avoid invalidating/refetching and undoing optimistic cache updates on failed save/publish, preventing form state from being wiped.
  • Add/expand CMS validation utilities to aggregate multiple field errors and forward details.errors[].path so the admin can highlight the specific invalid fields.
  • Remove lifecycle-hook-based validation that was operating on resolved component references, and register Koa middleware to validate the raw content-manager request body pre-write.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Moves workspace-level dependency overrides into the workspace config.
pnpm-lock.yaml Lockfile updates reflecting dependency graph changes from overrides/patch workflow.
patches/@strapi__content-manager@5.41.1.patch Patches Strapi admin document mutations to avoid cache rollback/invalidation on error.
package.json Removes pnpm overrides from root package.json (now centralized elsewhere).
cms/src/utils/pageLifecycle.ts Removes lifecycle validation hook support and associated validation execution.
cms/src/utils/navigationLifecycle.ts Removes navigation validation from lifecycle update path (validation shifts to middleware).
cms/src/utils/index.ts Re-exports newly added validators and merge helper from contentValidation.
cms/src/utils/contentValidation.ts Adds field-error aggregation and validator composition; expands validators to return path-aware errors.
cms/src/utils/contentValidation.test.ts Adds/extends Vitest coverage for validators, including path-aware error details and merging behavior.
cms/src/utils/blogLifecycle.ts Removes blog post validation prior to MDX export from lifecycles (validation shifts to middleware).
cms/src/index.ts Registers new Koa middleware to validate raw content-manager request bodies and forward error details.
cms/src/index.test.ts Adds tests for the body-validation middleware behavior and response shaping.
cms/src/api/summit-page/content-types/summit-page/lifecycles.ts Removes per-content-type lifecycle validation configuration.
cms/src/api/grant-page/content-types/grant-page/lifecycles.ts Removes per-content-type lifecycle validation configuration.
cms/src/api/foundation-page/content-types/foundation-page/lifecycles.ts Removes per-content-type lifecycle validation configuration.
cms/pnpm-workspace.yaml Adds CMS-specific overrides and wires patchedDependencies to the Strapi content-manager patch.
cms/pnpm-lock.yaml CMS lockfile updates reflecting patched dependency and resulting dependency graph changes.
cms/package.json Removes pnpm overrides from cms/package.json (now centralized elsewhere).
Files not reviewed (2)
  • cms/pnpm-lock.yaml: Generated file
  • pnpm-lock.yaml: Generated file

Comment on lines +57 to +70
export function mergeValidationErrors(
...validationErrors: Array<errors.ValidationError | undefined>
): errors.ValidationError | undefined {
const present = validationErrors.filter(
(err): err is errors.ValidationError => err != null
)
if (present.length === 0) return undefined
const allFieldErrors = present.flatMap(
(err) => (err.details as { errors?: unknown[] } | undefined)?.errors ?? []
)
return new errors.ValidationError(present[0]!.message, {
errors: allFieldErrors
})
}
Comment on lines 437 to 441
async afterCreate(event: Event) {
const { result } = event
if (!result) return
await runValidation(config, result)
if (shouldSkipMdxExport()) return

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.

2 participants