one owner per rule: chip grammar, defaults, links, params, names, loaders and client fixtures (drift audit 3, 4, 6-14) #244
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#244
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?
Parent
#234 (drift audit), items 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14 and the lower-priority node lookup.
Summary
Every remaining rule the audit found evaluated twice gets exactly one owner, and every rule the web client or a theme partial still needs is pinned by a fixture both suites load. Four of the copies already disagree in user-visible ways and are fixed as part of the move: a float chip param
2.0is rewritten as2, publish's draft-link check keys pages by a different URL than link resolve,harness sidecarand the loader accept different element names, and the git snapshot subject parser accepts uppercase language tags.Current behavior
Confirmed at
dcc1067. Each item: the copies, whether they disagree, and the owner this change gives them.3. Inline chip grammar (client vs engine).
web/src/rte/chip.ts(CHIP_START_RE,PARAM_RE,typedParamValue,chipParamText) re-implementsinternal/content/serialize.go(paramValueString,quotedSafe,validShortcodeName,validParamName) by hand. Disagreements:typedParamValueturns2.0into the JS number2,chipParamTextwrites2, Go re-lexes it as int64 and the file changes on a no-op edit (Go's serializer deliberately keeps.0;web/src/test/rte/markdown.test.tscovers only1.5). The client never refuses\n/\rin a value, Go does. Names are ASCII-only on the client and Unicode in Go.chipParamText's backtick fallback is a hand copy ofquotedSafe. No shared fixture.4. "Value spells its default".
elemops/save.gospellsDefault(skips required params, comparesParamValueStringof the value againstDefault) andl10n/hash.gocanonicalParam(includes required params,Type.Readfirst, compares againstEmpty()).spellsDefault's doc comment claims they agree; they differ on required params and on a type whoseEmpty()is not itsDefault(a bool with no default). Harmless for the hash today; the comment is a "same as X" marker.6. Placement. Go:
elemops/placement.goPlacementMsg(three axes) plus the.inline-parent rule incontent/mutate.gocheckInsert. Client:elementcrud.tshas the three axes twice (placementAllowed,insertRefusal) and the.inlinerule twice (insertRefusal,classifyDrop). Logic agrees today; no fixture.7. Link destinations. Scanning:
linkrefs/linkrefs.go(nextAnchor,forEachDestination) vsserver/publish.go(markdownLinkRE,hrefLinkRE,normalizeLinkTarget). They disagree on whitespace after](, on the angle-bracket form, and on ahrefwithout a closing quote on the line. Which params are links: linkrefs scans every string param and all front matter butaliases; publish useselemops.LinkParamValues(link/page-typed params only) and never page-typed front matter. Classification:server/linktargets.goexternalSchemeRE,web/src/components/SetLinkForm.tsxlinkTypeOfand the theme partialce/href.htmlagree on the scheme rule;normalizeLinkTargethas its own short scheme list. Extra, user-visible: publish keys draft-link targets bypageops.PageURL(the default pretty URL) while link resolve matchesNode.Permalink(honoursurl/slug), so a page with a customurlorsluggets a different answer from the two. No test ties any pair.8. Front-matter reads.
pagetree/build.golookupiscontent.MetaLookupagain. Draft is re-parsed inpageops/settings.goandpageops/translation_settings.go; url/slug inpageops/translation_addr.gometaStr. L10n mode constants are defined in bothpagetreeandl10n, read two different ways (MetaStringvscast.ToStringE).langcmd/content.gohand-builds the threeSetcallsl10n.FrontMatterChangesprovides, and itsmetaStringis unused. All agree today.9. Shortcode params.
content.Shortcodehas no accessor. Getters inl10n/l10n.go,elemops/uid.go,elemops/summary.go,elemops/slots.go,elemops/body.go,server/forms.go,server/pending.go,server/element_transfer.go,mediaops/scan.go; setters inl10n/l10n.go,elemops/uid.go,elemops/save.go; the same.Name <sort in eight places.setParamcopies on insert but overwrites in place on update;SetShortcodeEnabledfilters in place (sc.Params[:0]);SetShortcodeUID/setShortcodeDisabledappend and re-sort in place. No caller shares a backing array today, so the hazard is latent.10. Disabled flag.
server/forms.goshortcodeDisabled,web/src/api/content.tsdisabledByParams,scaffold/themes/deckle/layouts/partials/ce/disabled.htmlall accepttrueor"true". Agree; no exported Go reader, no fixture.11. Language tag.
^[a-z]{2,3}(-[a-zA-Z0-9]+)*$insiteconfig,langcmdandpagepath(each with a "copied, keep in step" comment).server/git.golangTagREaccepts[A-Za-z]{2,3}, so a hand-written[EN] …commit subject is read as a language.12. Name grammars. Element names:
sidecar/sidecar.go(theharness sidecargenerator)^[a-z0-9-]+$vssidecardir(the loader)^[A-Za-z0-9_][A-Za-z0-9_-]*$, copied again inaddcmd/children.go. They disagree both ways: the generator refuses uppercase and_the loader accepts, and accepts a leading-the loader refuses. Theadd-elementskill documents the loader's grammar. Param names:element/load.goASCII regex vscontent/serialize.goUnicode predicate; the element rule is the ASCII subset.13. Loader helpers and client mirrors.
requireVersion1andparseNameListare byte-identical inpagetype/load.goandbackendlayout/load.go;editormodule/load.goinlines the version check;element/load.goparseStringListis the same body onany. Client mirrors with no fixture, all agreeing today:pagemanage.tsisValidSlug(pageops.ValidSlug),mayPlaceUnder(pageopscheckChildAllowed),categorypath.ts(categories.SlugOf),rte/allow.tstoken list (element/schema.gorteAllowTokens),rte/markdown.tsTARGET_LINK_OPEN_RE(elemops/body.gocanonicalAnchorOpenRE, the security gate).14. Skill prose.
scaffold/site/.claude/skills/edit-content/SKILL.md("Rules, each one enforced by the engine or the theme") restates the rte token list, the paired/self-closing rule, the link grammar and the image rules. Three of its facts are not in AGENTS.md at all:container: trueimplies paired tags (elemops.BlockForSchema), the accepted image types (JPEG, PNG, WebP, AVIF; PDF for links), and the 2560px long-edge resize with EXIF/GPS stripping.migrate-site/SKILL.mdrepeats the image facts.Lower priority. Page-tree node lookup in
pageops/tree.gofindNode,server/modules.gofindNode(skips synthesized nodes) andserver/linktargets.gofindNodeByPath(does not). Only differs for an empty path, which no caller passes.Node.FindByPermalinkinpagetreealready applies the skip rule.Desired behavior
testdata/, loaded bygo testand by vitest (and, for the theme, by a Go test that renders the partial, asinternal/theme/disabled_test.godoes). Theinternal/pagepath/testdata/effective_type_cases.jsonpattern is the model; its loading recipe on the vitest side (theimport.meta.urlpath trick and why) becomes one shared helper inweb/src/test/so later fixtures do not copy it.2.0re-serializes as2.0; a no-op chip edit leaves the file byte-identical for every typed value in the fixture. A value containing a newline is refused by the client with the same error the server gives.urlorslug.harness sidecar <name>accepts exactly the names the loader accepts.pagepathaccepts.scaffold/site/AGENTS.mdgains the three missing facts (paired tags forcontainer: true, accepted image types, the resize/strip rule), each in the section where the related facts already live. Theedit-contentandmigrate-siteskills point at those sections and restate nothing.Decisions
SpellsEmpty-style function compares againstEmpty()and the callers state whether required params are exempt. Reason: the hash difference is harmless and changing it would re-flag translations.add-elementskill documents and what every existing site has loaded under. The generator andaddcmdcallsidecardir's exported validator.pagepathowns the language-tag grammar (it is already the stdlib-only leaf every other package can import).siteconfig,langcmdandserver/git.gocall it; the git parser's uppercase acceptance is dropped.linkrefsowns destination scanning and classification: it exports a destination iterator over a raw document and a classifier (external,page,anchor,media, aslinkTypeOfdistinguishes today). Publish scans through it (and so also sees page-typed front matter and all the params linkrefs sees; make the set of link-bearing params one function,elemops.LinkParamValuesor linkrefs's, not both). Publish keys targets by the same permalink resolve uses. Reason: the picker's classifier and the publish scanner are the two user-facing readers; linkrefs is the rewrite engine and must never miss a link the others see.content.ShortcodegetsParam,SetParam(keeps params sorted),DeleteParam, documented as never aliasing a caller's slice (copy-on-write). Every getter/setter/sort listed above goes through them;content/serialize.go's sorted-order check stays as the invariant.elemopsfunction; the client and the theme partial are pinned by a fixture of accepted and rejected spellings.insertRefusalis the only client copy (placementAllowedandclassifyDrop's.inlinecheck call it or go away), pinned by a fixture againstPlacementMsgpluscheckInsert.sidecardiris the natural home);parseStringList's "absent vs explicitnull" distinction is kept by the callers that need it and documented at the helper.pagetreenext toFindByPermalink, with the synthesized-node skip; the three callers use it.Rules touched and their owners (CLAUDE.md)
contentserializerelement.Param.inline)elemops.PlacementMsg,content.checkInsertlinkrefs,elemops.LinkParamValueslinkTypeOfandce/href.htmlcontent.MetaLookup,l10ncontent.ShortcodemethodselemopsdisabledByParamsandce/disabled.htmlpagepathsidecardircontentsidecardirpageops,categories,element,elemopspagetreeAffected docs and skills
scaffold/site/AGENTS.md: sidecar schema section (containerand paired tags), "Editing content by hand" (link grammar already there; add image types and resize/strip near "Library media publishes with the pages that use it"), element naming grammar stated once.scaffold/site/.claude/skills/edit-content/SKILL.mdandmigrate-site/SKILL.md: replace restated facts with pointers.scaffold/site/.claude/skills/add-element/SKILL.md: the name rule points at AGENTS.md.themes/deckle/README.mdonly if thece/href.htmlorce/disabled.htmlcontract wording changes (it should not).internal/element/agentsdoc_test.goandscaffold/skills_test.gostay green.Acceptance criteria
grep -rniE "same as|mirrors|keep (the two|them|in step)|identical to|agrees with" internal web/srcfinds no comment naming a second copy of a rule in this issue's list."and backtick, Unicode param name, value with newline (refused) — passes on both sides;2.0round-trips byte-identically through a client chip edit (vitest) and through the Go serializer.element.Parammethod decides "spells its empty value" and bothMergeParamsand the l10n hash call it;TestHashSpelledOutDefaultIsNoChangeandTestMergeParams*still pass; a new test asserts the two callers agree on every param of every scaffold sidecar.web/src/elementcrud.tshas one placement predicate, fixtured against Go.linkrefsexports destination scanning and classification;server/publish.gohas no link regex of its own; a test shows publish and link resolve agree on a page with a customurland one with a customslug;linkTypeOfandce/href.htmlare fixtured against the classifier.content.ShortcodehasParam/SetParam/DeleteParam; no other package indexessc.Paramsby name or sorts them; a test asserts the setters never write into a caller's backing array.disabledByParamsandce/disabled.htmlfixtured against it.pagepath;siteconfig,langcmdandserver/git.gocall it; a test shows[EN] subjectis not parsed as a language.harness sidecarandharness addvalidate names withsidecardir's exported rule; the sidecar generator acceptsMyBlock_2and refuses-foo, matching the loader.requireVersion1/parseNameList/the inline version check exist once;pagetype,backendlayout,editormoduleandelementloaders call it.pagetreeexports the node lookup;pageops/tree.go,server/modules.go,server/linktargets.gouse it.langcmd/content.gousesl10n.FrontMatterChanges; its unusedmetaStringis gone;pagetree/build.gousescontent.MetaLookup; one set of l10n mode constants.edit-content,migrate-siteandadd-elementskills restate none of them; drift tests green.How to verify
go vet ./... && go test ./...;cd web && npm test(fixture tests included);harness check --site scaffold/site.ratio=2.0(add one to a scaffold test page), save the body unchanged,git diffis empty.url: /custom/linked from a draft and a published page; the publish warning and the link picker's resolve agree.harness sidecar my_block --site <tmp site>succeeds;harness sidecar -badis refused with the loader's message.Assumptions to check
2as int and2.0as float (read from the serializer's comment andserialize_test.go; the Hugo module source is not vendored here). Verify with a round trip throughcontent.Parse.contentmay import nothing that knows schemas; ifLinkParamValuesmust stay inelemops,linkrefstakes the param set as an argument rather than importingelemopsin a cycle.deckle-hosted(read-only import in this instance) or the scaffold bundles has an element name the loader grammar accepts but the generator previously refused, or vice versa; the switch must not make an existing site failharness check.requiredexemption inspellsDefaultis still what #219's fix intended; its tests say so.Out of scope
SpellsEmptydecision preserves (none expected; if the hash changes for any scaffold fixture, stop and say so in the PR).internal/removecmd/strip.go(intentionally separate, see #232).Blocked by
server/linktargets.go,elemops/summary.go,server/pending.goandweb/src/api/content.ts; landing the payload change first keeps this rebase small)