Deepen the element mutation engine: move shortcode-shaping semantics out of internal/server #111
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#111
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Found by an architecture review (deepening opportunities — turning shallow modules into deep ones).
Files:
internal/server/server.go(handleSavePage, ~190 lines),internal/server/element.go,internal/server/uid.go,internal/server/slots.go,internal/server/body.go; contrastinternal/content/mutate.go,internal/element,internal/pageops/settings.go.Problem:
internal/contentexposes only structural mutation (InsertChild/RemoveChild/MoveChild), so all semantic element shaping — uid stamping, theslotwrapper, item-shortcode composition, param merging, the reserved-param vocabulary — lives as ~20 free functions insideinternal/serverthat hand-manipulatecontent.Shortcodeinternals (newShortcode,mergeParams,partitionItems,buildItemShortcodes,stampUIDs,setShortcodeDisabled,coalesceText, …). The validate → coerce → apply save contract is written twice (server.handleSavePageandpageops.SaveSettings, including the<link>Targetcompanion set/delete dance in both). The four-way child-composition switch inhandleSavePage(server.go:771-790) is only testable by POSTing JSON at anhttptestserver over a temp site.Solution: deepen a single element-tree module (a neighbor of
content/element, not inside the server) whose interface is roughly "apply this validated edit to this element" — absorbing uid stamping, slot wrapping, item composition, and the save contract.handleSavePageshrinks to parse request → validate → apply → write;pageops.SaveSettingscalls the same module. The duplicated front-matter map readers (server.metaStringvspageops.metaLookup) fold into the same cleanup.Benefits: Locality — the on-disk shape of an element is currently understood only by crossing content ↔ element ↔ server three times; it would live in one place. Leverage — the server and
pageopsbecome thin callers. Tests move from HTTP-over-tempdir to direct through-the-interface tests on the composition logic, which is where the real bug surface lives (index-path staleness acrossRemoveChildtext-merge, the four-axis placement interaction).Agent Brief
Category: enhancement
Summary: Extract element-tree semantic mutation — uid stamping, slot wrapping, item-shortcode composition, param merging, the validate→coerce→apply save contract — out of the HTTP server package into a dedicated module that both the server and
pageopscall.Current behavior:
internal/contentexposes only structural mutation (InsertChild/RemoveChild/MoveChild). Everything semantic about shaping an element lives as free functions inside the HTTP server package (internal/server):newShortcode,mergeParams,partitionItems,buildItemShortcodes,stampUIDs/restampUIDs,setShortcodeDisabled,coalesceText, and the slot-wrapper family (ensureSlotWrapper,topLevelSlot,slotIsEmpty, …). The page-save handler contains a four-way items/body child-composition switch with documented byte-for-byte compatibility promises (legacy self-closing elements upgrade to paired tags on any items save; interleaved newline text nodes are coalesced when items are extracted; a body-capable element is canonically a block even with an empty body). The validate → coerce → apply contract is written twice — once in the element save handler, once inpageops.SaveSettings— including the<name>Targetlink-companion set/delete dance in both (seeelement.LinkTargetSuffix). Two near-identical front-matter map readers exist (metaStringin the server,metaLookupinpageops). The composition logic is only testable by POSTing JSON at anhttptestserver over a temp site.Desired behavior:
A single element-mutation module — a neighbor of
contentandelement, not inside the server package — owns semantic element shaping behind an interface of roughly "apply this validated edit to this element". It absorbs uid stamping, slot wrapping, item composition, param merging, and the shared save contract. The HTTP save handler shrinks to parse request → validate → apply → write;pageops.SaveSettingscalls the same module for the shared parts. This is a behavior-preserving refactor: on-disk output must stay byte-for-byte identical, including every branch of the four-way composition switch and the<name>Targetcompanion semantics. Move first, mechanically; do not "improve" those semantics.Key interfaces:
internal/content/internal/element) exposing the semantic mutation API;internal/serverandinternal/pageopsbecome thin callers.content.Shortcodeinternals should stop being hand-manipulated from the server package; the new module is the one place that knows an element's on-disk shape.element.LinkTargetSuffixcompanion set/delete, defined once and called from both element save andpageops.SaveSettings.metaString/metaLookuptwins.Acceptance criteria:
pageops.SaveSettingscontain no duplicated coerce/apply or<name>Targetcompanion logic; both delegate to the shared module.go test ./...), including content roundtrip/fidelity/fuzz tests and the existing HTTP-level tests — they are the byte-for-byte compatibility net.scaffold/site/AGENTS.mdis checked for drift; content canonicalization rules it documents must still be accurate (a pure move should not change them).Out of scope: