Skip to content

feat(cms): add, replace, remove and reorder blocks by key with blockEdits - #897

Open
kmonsoe wants to merge 1 commit into
feat/cms-text-replacementsfrom
feat/cms-block-edits
Open

feat(cms): add, replace, remove and reorder blocks by key with blockEdits#897
kmonsoe wants to merge 1 commit into
feat/cms-text-replacementsfrom
feat/cms-block-edits

Conversation

@kmonsoe

@kmonsoe kmonsoe commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Stacked on #895 — merge that first, then retarget this PR to main before merging it. Independent of #896 (KB), which is stacked on the same parent.

Why

#895 made it possible to reword a block without regenerating the article, but changing which blocks exist still meant sending the whole array through data. On a real journal entry that array is ~20 blocks and most of the payload, so adding one callout re-transmits the entire post and risks the same drift the text replacements were introduced to avoid.

The live journal-blocks entries already carry authored, stable keys (lead, premise, takeaways, close), so key addressing works on existing content without a migration.

What

cms_update_entry gains blockEdits — an ordered, all-or-nothing list of operations on a blocks field.

op effect
set replaces the block with that key, or inserts it when the key is new
delete removes the block with that key
move repositions it; requires one of before / after / position
  • before and after name another block's key; position is start or end. At most one of the three per edit.
  • set carries the whole block (type plus every prop) and replaces the old one outright rather than merging, which keeps the operation idempotent. Omitting key mints one, so a block can be appended without inventing an identifier.
  • Failures write nothing and do not move the version: cms_block_not_found for an unknown target or anchor, cms_block_ambiguous when two blocks share the addressed key, cms_block_invalid for a malformed edit.
  • Edits apply in order, so an insert and a subsequent move of that same block are one call.
  • blockEdits and textReplacements compose on the same field, block edits first. data remains exclusive with both.
  • Asset and reference rewiring runs on the resulting array, so a figure block inserted this way is immediately covered by the delete guard.
  • The /v1/cms/drafts/:id PATCH accepts the same field.

Design notes

The op enum sits inside the array elements, not on the tool input, so the schema stays a flat object with an enum — no oneOf, which is the shape strict-validation hosts have rejected. This mirrors Sanity's patch_documents, where one call carries a heterogeneous array of set / unset / insert operations.

set deliberately upserts rather than splitting into separate create and update ops: it is the one place the repo's configure_* precedent applies cleanly, since the key is the identity and a re-call is unambiguous.

Skills

author-with-blocks gains an operations section, and revise-entry now frames the two surgical inputs together: replacements change what a block says, block edits change which blocks exist. Fixtures regenerated.

Tests

  • cms.fields.test.ts: 11 cases over the pure helper — in-place replace, append, before/after/start/end placement, minted keys, replace-and-reposition, ordered edits, empty and non-list values, and each failure code.
  • cms.service.test.ts: insert/replace/move/delete through the service, minted key shape, atomic failure leaving the entry untouched, block-type validation of the result, non-blocks and unknown fields, data overlap, and block-edits-before-replacements ordering.
  • cms.integration.test.ts: the surface through /mcp, asserting cms_list_asset_usage picks up an inserted figure and cms_delete_asset then refuses.

37 files / 589 tests pass across the CMS, MCP, control-plane and common suites; workspace typecheck clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJjhaB9bMU1agbqVKTwbB1

…dits

cms_update_entry takes blockEdits — ordered, all-or-nothing set/delete/move
operations addressing blocks of a blocks field by their stable key, placed with
before/after/position — so changing which blocks exist no longer means resending
the array. set replaces a block outright or inserts a new one, minting a key
when none is given. Unknown and duplicate keys fail with cms_block_not_found and
cms_block_ambiguous, writing nothing. Block edits run before text replacements
when a call carries both, and asset rewiring runs on the result so an inserted
figure is covered by the delete guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJjhaB9bMU1agbqVKTwbB1
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