fix(items): one canonical row layout owned by a single split/compose #239
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!239
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/232"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #232
What
An items owner's children (its item rows plus its body region) now have one canonical layout: the body region, a line break, then the rows one per line, with no blank line between. One split/compose pair owns that layout:
elemops.SplitItems/elemops.ComposeItemsininternal/elemops/items.go. The two are exact inverses on canonical input.GET /api/element/bodyor into the translation hash.server/body.go)ApplyEdit(split, replace the axes the request sent, compose) andWouldClobberBodytranslatedItemsRefusall10n.bodyContent(the hash)Repair,PlaceandExtractTranslatededit an owner's rows on the split and write the owner back canonically, and only when they change it.elemops.IsItemis the only check for "is this child an item of its parent".l10n.isItemis gone, andholdsElement,StampSource,markTranslatedandserver.itemAtcallIsItem.content.MergeTextis the only adjacent-text merge.removeAt, the split,bodyContentandspliceNodesuse it, andcoalesceTextand the hand-written merges are gone.removecmd/strip.gois untouched.scaffold/site/AGENTS.mddefines the layout once, under "Editing content by hand". The "Item collections" paragraph, theimage/text-media/cardssidecar comments and theedit-contentandmigrate-siteskills point at it. The showcase page and the AGENTS.md translation example were made canonical; each had a blank line before its row.Assumptions from the issue, checked:
serializenever flushes a pending block separator).imgandcardonly push onto the parent's Scratch, and the body comes from.Inner.placeIndex/ leading needed the fix above.Tests
elemops/items_test.go: split/compose table with 15 layouts, including blockcardrows, a body ending in an inline or block shortcode, and glued or interleaved input. Covers the inverse property, re-parse equality and no mutation of inputs.content:TestMergeText.server/items_layout_test.go:"Body.", and the save is byte-identical.checkcmd.Runaccepts the result.l10n:InsertTranslatedandExtractTranslatedkeep owners canonical.go vet ./...,go test ./...andcd web && npm test(2851 tests) are green.The branch has a no-op merge of
ea11f78on top, which leaves the tree unchanged: my first commit was accidentally parented on a stale localmain, and I could not rewrite the pushed branch. The PR diff againstmaincontains only this change.Release note
image,text-media,cards, or any element with anitemscollection) whose file is not yet in the canonical layout rewrites that one element into it. That includes every owner the editor itself wrote before this change, with rows glued on one line before the body. Other elements on the page are not touched, andharness checkstays silent about row layout.🤖 Generated with Claude Code
An items owner's children (its item rows plus its body region) now have one canonical layout — the body region, a line break, then the rows one per line — owned by elemops.SplitItems/ComposeItems, exact inverses on canonical input. Every reader and writer goes through the pair: the body GET, ApplyEdit and WouldClobberBody, the form's row list, the translated items refusal, the translation hash and the translation structure repair. - Split is lenient (any parseable layout opens) and strips the body region's edge newline runs, so they no longer leak into the served body or the translation hash; compose is strict, so one save reaches the fixed point and saving a canonical owner is byte-identical. - elemops.IsItem is the one "is this child an item" predicate; l10n's isItem and holdsElement, and server's itemAt, call it. - content.MergeText is the one adjacent-text merge; removeAt, the split, bodyContent and spliceNodes use it (coalesceText and the hand merges are gone). - Repair edits an items owner's rows on the split and writes the owner back canonically, only when it changes it; an item's "leading" is computed among its owner's rows, so a first item lands first behind a body. - AGENTS.md documents the layout once ("Editing content by hand"); the sidecar comments and the edit-content and migrate-site skills point at it. Closes #232 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>ea11f78' (main) into afk/232[autoland] verdict: pass
Verdict: CONCERNS (non-blocking): approved, not auto-merged
Signal relied on:
labctl pr checks 239=success.ci / build-and-testran the frontend install, build and test, the hugo install, andgo vet/test/build, and passed in 16m35s.ci / releasewas skipped because it only runs on push to main. I did not re-run any of it.Concern: branch history would put a 36k-line commit on main
f9f6262(fix(items): …) is parented onfb92872, main as of #198, not onea11f78. Its tree already holds everything up toea11f78plus the fix. Against its own parent its diff is 334 files, +36,253 / −3,621. The trailing merge8d69d4ais a no-op on the tree.labctl pr mergeuses a fixed method), so merging as-is putsf9f6262on main for good.git blame 8d69d4a -- web/src/i18n/en.json, a file this PR does not touch, credits all 1,173 lines to "fix(items): one canonical row layout…". On main they belong to the #229 i18n commits. The same applies to every file changed between #198 and #235.afk/232as one commit onea11f78. The tree diff against main is correct, at 27 files, so either route lands exactly this change.Code review: no findings
elemops.SplitItems/ComposeItems(internal/elemops/items.go) are the one owner of the layout. Every reader and writer named in the issue goes through them:server/body.go:92elemops/save.go:121(ApplyEdit) and:152(WouldClobberBody)server/forms.go:609server/translation.go:451l10n/hash.go:201l10n/structure.go, viaelementList/openList/commitelemops.IsItemis the only item predicate.content.MergeTextis the only text merge, andcoalesceText,lastTextand the merge inspliceNodesare gone.removecmd/strip.gois untouched.orderElementscommits only whenreordered).indexOfcannot return -1 inindexSource/removeSite, because the split keeps each shortcode by identity in exactly the regionregion()picks.themes/deckle/layouts/shortcodes/img.htmlrenders nothing when it has a parent, so dropping the blank line before the rows (showcase page, AGENTS.md example) changes no output.image/text-media/cardssidecar comments, and theedit-contentandmigrate-siteskills. The drift tests are green in CI.Closes #232and the release note are all present.Correction to the line references in my approval: the
SplitItemscall sites areinternal/elemops/save.go:126(ApplyEdit) and:153(WouldClobberBody), andinternal/l10n/hash.go:203. The approval cited 121, 152 and 201. The other references are correct.