One generic sidecar-loader skeleton behind element/pagetype/backendlayout LoadDir #114
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#114
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/element,internal/pagetype,internal/backendlayout(each with its ownLoadDir), plus the media/pagetype loaders.Problem: the directory-scan contract — one
<name>.yaml|.ymlper file,.yamlwins over.yml, the^[A-Za-z0-9_]…name regex, per-fileLoadError, subdirs/other extensions ignored, missing dir = empty + no error — is re-implemented per package with near-identicaldoc.goprose. (The param vocabulary was correctly deduped viaelement.ParseParams; the directory-scan/dup-resolution/strict-decode skeleton was not.)Solution: a generic sidecar-loading module the three packages call with their decode function; each keeps its own schema types and validation.
Benefits: Locality — the loading contract (which
scaffold/site/AGENTS.mddocuments andinternal/element/agentsdoc_test.gomechanically guards) is defined once. A behaviour fix lands in one place instead of four, and the contract gets one thorough test suite instead of four partial ones. Note: any observable change to sidecar loading must updatescaffold/site/AGENTS.mdin the same change (CLAUDE.md rule) — a pure dedup should be behaviour-preserving.Agent Brief
Category: enhancement
Summary: Extract the shared sidecar directory-scan skeleton behind
element.LoadDir,pagetype.LoadDir, andbackendlayout.LoadDirinto one generic module; each package keeps its own schema types and validation.Current behavior:
Three packages —
internal/element,internal/pagetype,internal/backendlayout— each implement their ownLoadDir(dir string) ([]Schema, []LoadError)with a near-identical skeleton:os.ReadDirwith missing-dir →(empty, empty)and any other read error → a singleLoadErrornaming the dir; subdirectories and non-.yaml/.ymlentries skipped; filenames sorted; name derived by trimming the extension; duplicate base names resolved first-seen-wins in sorted order (sox.yamlloads andx.ymlbecomes a "duplicate name"LoadError); the shared^[A-Za-z0-9_][A-Za-z0-9_-]*$name rule; per-fileLoadErrorisolation (one bad file never hides the rest); schemas sorted byName, errors byFile; both returned slices never nil. Each package also carries its own structurally identicalLoadError{File, Msg}type with the samejson:"file"/json:"error"tags, and its own doc prose restating the same contract.Desired behavior:
The directory-scan / duplicate-resolution / per-file-error skeleton is defined once in a generic module (likely type-parameterized), which the three packages call with their per-family pieces: a family noun for error messages ("element", "page type", "backend layout"), and a decode+validate function for one file's contents. Each package keeps its own schema types, raw decode targets, and validation logic unchanged. All observable behavior — return values, error messages, orderings, JSON shapes — is preserved exactly.
Note: the package name
sidecaris already taken by the shortcode-scaffold generator (internal/sidecar), so the new module needs a different name.Key interfaces:
LoadDir(dir string) ([]Schema, []LoadError)signatures must not change — they are called by the server layer and theirLoadErrorJSON (file/errorkeys) is an HTTP contract.LoadErrormust keep marshalling to the same JSON. Whether the three types become aliases of one shared type or stay per-package wrappers is the implementer's call, as long as the wire shape andError()string (": ") are unchanged.internal/elementruns a cross-schema pass after per-file loading (rteallowedInlineanditems.elementresolution) that needs each loaded schema paired with its source filename to report errors against the right file — the generic module must expose that pairing.internal/pagetypehas a pre-check that rejects the retired_rootbase name with a fixed explanatory message. Watch the ordering edge: today every_root.*file gets the obsolete-message error (it never enters duplicate tracking), and separately a file whose decode fails still claims its base name, so a same-named sibling reports "duplicate" — both orderings must survive the extraction.Acceptance criteria:
os.ReadDir/extension-filter/sort/duplicate-tracking loop..yaml-beats-.ymlduplicate resolution, duplicate reported even when the first-seen file failed to decode, invalid-name rejection, per-file error isolation, output sorting, never-nil slices./api/...error payload shape for bad sidecars is unchanged (LoadError JSON keysfile/error).scaffold/site/AGENTS.mdneeds no edit (behaviour-preserving) andinternal/element/agentsdoc_test.gostill passes;go test ./...is green.Out of scope:
parseNameList/requireVersion1(duplicated betweenpagetypeandbackendlayout) — reasonable follow-up, but not required here; include only if it falls out naturally in the same module without widening the diff.internal/sidecarshortcode-scaffold package and the media loaders — no directory-scan duplication lives there.