Fix #867: loosen PT _key validation and fix template seeds#869
Fix #867: loosen PT _key validation and fix template seeds#869
Conversation
… autosave Fixes #867. Templates whose seed.json omits `_key` on Portable Text blocks would round-trip valid through initial render but fail autosave validation: the Zod schema generated for portableText fields requires `_key: z.string()` on every top-level block, and the admin's autosave path re-sends the data it loaded unchanged. applySeed now passes content data, section content, and widget content through a normalizer that injects stable, deterministic keys on every PT-shaped node missing one. Existing keys in the seed file are preserved; generated keys avoid colliding with explicit ones.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | d9623ae | May 01 2026, 06:15 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | d9623ae | May 01 2026, 06:16 AM |
🦋 Changeset detectedLatest commit: d9623ae The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 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 |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | d9623ae | May 01 2026, 06:16 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | d9623ae | May 01 2026, 06:15 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | d9623ae | May 01 2026, 06:16 AM |
|
I have read the CLA Document and I hereby sign the CLA 1 out of 2 committers have signed the CLA. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@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-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Fixes autosave validation failures for content seeded from templates by normalizing Portable Text nodes to include stable _key values at seed-ingestion time (keeping runtime/admin validators strict).
Changes:
- Added
normalizePortableTextKeys()helper that walks seed payloads and injects missing_keyvalues on PT-shaped nodes while preserving existing keys and avoiding collisions. - Wired PT key normalization into seed application for content entries, sections, and widget content.
- Added unit + integration regression tests and a patch changeset.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
packages/core/src/seed/normalize-portable-text.ts |
Implements deterministic, collision-avoiding _key injection for PT-shaped nodes during seeding. |
packages/core/src/seed/apply.ts |
Applies the normalizer after $ref/$media resolution for content, and before persisting sections/widgets. |
packages/core/tests/unit/seed/normalize-portable-text.test.ts |
Unit coverage for immutability, recursion, preservation, and collision-avoidance behavior. |
packages/core/tests/integration/seed-portable-text-keys.test.ts |
End-to-end regression ensuring seeded PT survives the same validator used by autosave. |
.changeset/seed-portable-text-keys.md |
Patch release note describing the seeded Portable Text normalization fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@ask-bonk pause this — it's a regression of #777, not a templates bug, and the fix is at the wrong layer. Audit before re-rolling. What changed: Full audit (seed each template with no normalizer, then re-validate every stored entry against
The Drop the seed-time normalizer. It only helps fresh installs (setup applies seed once with Do this instead:
Tests: keep Also: rebase. Branch is missing #868. |
|
Pausing as requested. I re-audited every claim before responding; you're right on all the verifiable points. Posting findings rather than re-rolling code in this run so we agree on direction first. What I verified1. This is a regression of #777, not a templates bug. PR #777's body: "Validation moved to the runtime. 2. The
3. The Portfolio seed audit (
I couldn't reproduce your 4. Seed-time normalizer is the wrong layer. Confirmed in 5. Branch is missing #868.
On scope discipline (where I disagree slightly)The PR body's "out of scope: actual seed.json files still ship without Proposed re-rollSmaller PR, in this order:
Strict- Awaiting confirmationWant me to proceed with that plan? If yes, I'll close this branch's commits with a fresh re-roll on top of For full transparency: my original investigation didn't catch the |
|
Posted. Pausing as requested — no code changes in this run. The maintainer's audit was correct on every verifiable point (the regression came from #777, the |
|
@ask-bonk implement it as suggested |
|
To https://github.com/emdash-cms/emdash.git |
|
@ascorbic Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
PR template validation failedPlease fix the following issues by editing your PR description:
See CONTRIBUTING.md for the full contribution policy. |
|
@ask-bonk try again |
|
and make sure you update the PR title and description |
Replaces the seed-time normalizer in the previous commits with the
two-layer fix the maintainer's audit landed on:
1. zod-generator.ts: `_key` on Portable Text blocks is now
`.optional()`, and non-required fields use `.nullish()` instead
of `.optional()` so the validator accepts the `null`s the DB
stores for unset columns. Both regressions trace back to PR #777
wiring the existing schema into the runtime content-update path
(autosave) without auditing whether stored data round-trips.
2. Template seeds: every shipped `seed.json` (blog, portfolio,
starter, marketing, plus the four `*-cloudflare` variants) now
ships stable `_key` values on every Portable Text block / span
/ mark def. The portfolio template's bare-URL `featured_image`
values are rewritten as `$media` references in the same shape
the blog template already uses (the validator's `image`
schema is `z.object({id, ...})`, so bare strings were a second
latent autosave failure the original PR didn't address).
Drops the runtime normalizer and its unit suite -- the failure mode is
gone at the data level. The integration test that previously exercised
the normalizer is repurposed as a regression guard: every shipped
template seed is applied to a fresh DB and every stored entry is
re-validated against `validateContentData(..., { partial: true })`,
which is exactly what the autosave endpoint runs. Template changes
that break the contract fail before release.
Closes #867.
|
Re-rolled per the audit. Summary of what changed in this push (commit Validator (
Templates (all 8 shipped
Tests / dead code
Verification (Node 20 environment, so admin/auth deps don't build cleanly here):
Branch state: rebased through PR title and description updated to use the required template; AI disclosure box ticked. |
|
Re-rolled the PR per the audit. Summary: At the validator (
At the data (all 8 template
Tests / dead code:
PR meta:
Verification on this Node 20 runner (admin/auth deps don't build because they need
|
|
/review |
|
OK, 20 matches the description. None of them specifically test Summary of my findings:
These are not bugs. The PR is solid. LGTM! |
What does this PR do?
Fixes autosave validation failures on content scaffolded from the
blog,portfolio, andstartertemplates. PR #777 wired the existinggenerateZodSchema()validator into the runtime content-update path, so autosave now revalidates the body the admin re-sends on every keystroke. Several first-party templates ship seed content that the schema rejected, so users couldn't save edits to seeded entries.Two related issues, both fixed at the right layer:
_keywas strictly required on Portable Text blocks even though the rest of the block schema is.passthrough()and the editor regenerates_keyon every change. Requiring it on input rejected legitimate seed/import data without protecting any real invariant._keyis now.optional()in the validator (packages/core/src/schema/zod-generator.ts).SchemaRegistry.addFieldColumnadds them withoutNOT NULL), but the validator used.optional()which only acceptsundefined. Any field that's actuallynullin the DB failed round-trip on autosave. Switched to.nullish()._key, and portfolio'sfeatured_imageas bare URL strings (the schema validatesimageasz.object({id, ...})). Fixed the seed files directly and added a regression guard that runs every shipped template seed throughvalidateContentData(..., { partial: true })-- the same path the autosave endpoint uses.The runtime normalizer from the original commits and its unit suite are dropped; the failure mode no longer exists at the data level. The integration test from those commits is repurposed as the regression guard above.
This is a re-roll of the original approach following the maintainer's audit (thread), which correctly identified that:
onConflict: "skip");featured_imageissue was a separate latent bug the original PR ignored.Closes #867.
Type of change
Checklist
pnpm typecheckpassespnpm lintpasses (diagnostic count unchanged from baseline; remaining diagnostics are pre-existing cross-package "error type" warnings)pnpm testpasses (or targeted tests for my change) -- seed, schema, and api tests all green; the 8 vite-config test failures and 25 file-load failures in the local run are environmental (Node 20 vs--run, unbuilt admin/auth deps), not from this change, and pre-exist on the branch tippnpm formathas been runAI-generated code disclosure
/bonkScreenshots / test output
The new
tests/integration/seed-portable-text-keys.test.tsruns the full seed → DB → validator round-trip for all 8 shipped template seeds (blog, blog-cloudflare, portfolio, portfolio-cloudflare, starter, starter-cloudflare, marketing, marketing-cloudflare) and asserts every stored entry survives the autosave validator. This is the regression guard the maintainer asked for.