feat!: redesign argument and type system as v6 (core + shims, golden-tested)#334
Merged
Conversation
- Inventory of ten confirmed defects and design-debt items in InitArgs.html and InitTypes.html, each mapped to a future test - Agreed decisions: clean core plus compatibility shim inside v5, fully recursive validation, golden-file test harness, cached schema compilation, camelCase-only canonical keys - Architecture: ArgsSchema.html compiler (partialCached), recursive inline validator, Args.html entry point with separated envelope, InitArgs/InitTypes reduced to thin shims - Phased rollout with characterization-first golden tests and a warnings-first strictness transition Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Document the live-editing sandbox expose-glob placement rule for new partials and the null-vs-explicit value distinction required by CloudCannon-authored content - Add CloudCannon-shaped golden cases and child-structure coverage to the test harness section - Correct the phase-3 gate: the mod-blocks exampleSite is an empty placeholder and provides no integration signal until seeded Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Eleven bite-sized tasks: golden harness, five characterization groups, ArgsSchema compiler, Args entry point with recursive validator, InitArgs/InitTypes shims, schema caching, docs - Load-bearing mechanics verified live before planning: JSON output template resolution, the dropped data mount in the exampleSite, and an end-to-end InitArgs call demonstrating the nested-default defect - Every intentional behavior change is gated by an enumerated golden-diff review category Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add data mount to exampleSite/hugo.toml to expose module's data files - Create test-runner layout at exampleSite/layouts/tests/single.json to render InitArgs result envelopes as JSON - Create test fixture files: structure, case group, and content page for envelope characterization test - Add golden comparison script (tests/golden.mjs) for comparing generated test output against committed golden files - Update package.json scripts: add pretest, test, and test:update commands - Generate initial golden baseline that characterizes BUG(5): user argument 'default' is clobbered by bookkeeping 'default' slice Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add test fixtures for static, config-based, and deprecated defaults - Add test fixtures for type casting and collection types - Add test fixtures for select validation and numeric ranges - Capture BUG(3): explicit false skips type validation - Capture BUG(4): false config value falls through to static default - Document latent quirks: empty string matches float regex, float64 type handling Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…avior
- Fix dict-case comments: both plain map and slice-of-maps forms ERROR
today; the legacy 'dict' alias ([]map[string]interface {}) never
matches real YAML data
- Document why wrong-type-errors uses a string instead of the composite
[1, 2]: that form crashes the whole Hugo build (findRE at
InitArgs.html:154 cannot cast []interface {} to string), so it cannot
be pinned in a golden
- Goldens unchanged (comment-only edits to the case file)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add test-required.yml fixture with required args, positional args, and group-scoped arguments - Add positional test cases: both-positions, first-position-only, excess-position-errors - Add required test cases: required-provided, required-missing-errors, group-filter-skips-partial-arg, group-partial-requires-both, no-group-requires-all, first-error-wins - Pin BUG(6) behavior: first-error-wins reports exactly one errmsg - Verify group-partial-requires-both correctly errors on missing partial-only argument - Add content pages positional.md and required.md for test group generation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ping
- Add nesting group: 7 cases pinning BUG(1) (nested defaults use parent nil)
and BUG(2) (zero-deep validation of nested members)
- Add child group: 3 cases verifying child-structure merging with cascade
and merge parent-flag behavior
- Add frontmatter group: 2 cases comparing params-typed nested maps against
data-typed variants to detect maps.Params type-matching blind spot
- Create shadowing _types.yml in exampleSite with module sync note
- Golden check passed (9 groups); no new latent defects found
Key findings:
- BUG(1) confirmed: absent-udt-shape-fill shows {"align": null, ...}
instead of {"align": "start", "width": 8, ...} (member defaults ignored)
- BUG(2) confirmed: nested-wrong-type-passes allows align: 42 (select type)
- No maps.Params blind spot: params-typed-nested-map and heading-valid
produce identical output, both pass validation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Pin blueprint derivation from Bookshop spec (test-hero.bookshop.yml) - Verify sidecar merge with kebab-case keys (test-hero.yml) - Document CloudCannon null-heavy payload handling (err: false preserved) - Capture snake/kebab normalization with prefer-snake_case warning - Verify blueprint scalar defaults (link_type, width) survive merge - Note: blueprint show_more: false not preserved when arg is null (candidate defect) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…se comment Move the sidecar-merge default-survival observation into the case file, as required by the task brief: link_type and width defaults are applied for null keys, show_more's false default is never applied (legacy InitArgs gates defaults with `or $def.config $def.default`, and false is falsy), and heading.align stays null. Comment-only change; goldens unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compile structure/bookshop argument definitions (global _arguments.yml,
_types.yml, structure files, blueprint+sidecar, and parent-flagged child
arguments) into a self-contained recursive schema tree, keyed by
(structure, bookshop, child). Restructures the generated compile-node
inline partial to use a single, unconditional trailing return instead of
an early-return-plus-final-return pair: this Hugo version rejects a
second `return` in the same defined template ("wrong number of args for
return: want 0 got 1"), even when only one is ever reached at runtime;
the restructuring preserves identical semantics (verified against the
`with`-is-falsy behavior of an empty dict).
Adds a `schema` golden group (11th group) exercising scalar structures,
nested UDT recursion to depth 3, child-structure merging, bookshop
blueprint+sidecar merging, and compile-time error paths. All 10
pre-existing golden files are byte-identical; InitArgs.html and
InitTypes.html are untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two accepted design-gap fixes in the ArgsSchema compiler: - Package-qualified Go reflect-type names declared in _arguments.yml (e.g. template.HTML, hstring.HTML) no longer emit 'schema: unknown type'; they are collected verbatim on the node as an optional 'reflects' field ([]string, declared order) for a printf "%T" match at validation time, restoring legacy parity. Dot-less unresolvable type names still error. - Child error messages are appended element-wise instead of as whole slices, defeating a Hugo append type flip-flop that nested sub-arrays into errmsg when zero-error and error-producing UDT members interleaved. nested-structure and bookshop-blueprint now compile err:false with empty errmsg; compile-errors keeps exactly its two real messages as a flat array. Only the schema golden changed; the 10 pre-existing goldens remain byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fixes nested defaults, zero-deep validation, falsy-value skips, and or-based config fallthrough on the strict path (spec defects 1-4, 6) - Adds side-by-side strict results to every golden group Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BREAKING-ISH (warnings only): newly detectable validation findings
(nested type mismatches, unknown nested attributes, falsy-value type
mismatches) surface as warnings; promotion to errors follows one
release cycle later per the design spec.
Golden diffs in this commit are intentional: nested defaults fixed,
all errors reported (no first-error break), explicit-false site
params honored, camelCase duplicates added for snake_case keys,
excess-positional-argument err now reflects errmsg length, and two
pre-existing merge/type-alias bugs are fixed at the ArgsSchema/Args
level (required to unblock the Hinode smoke test, see below).
Also fixes three latent bugs in the shared Args.html/ArgsSchema.html
core, exposed for the first time by routing every InitArgs caller
through it:
- numeric range/min-max checks no longer get the falsy-value
exemption meant only for type casting (legacy never exempted them
either; fixes bookshop/cloudcannon-explicit-false staying err:true)
- unknown-type detection now accepts bracket/asterisk Go type names
([]string, map[string]interface {}), not just dotted ones, fixing
a hard build crash in LogErr/LogWarn's own argument validation
- same-named global/local arguments that redeclare "type" no longer
leak the global's default/config/options via merge's recursive
nested-map behavior (fixed a hard build error on every Hinode page
via button.yml's deprecated "size" colliding with the global
heading "size", and fully resolves envelope/default-arg-collision)
Hinode exampleSite smoke test: build now completes (previously hard
crashed on the first logged warning anywhere). 45 remaining ERRORs
are confined to mod-fontawesome's fas/fab/icon shortcodes, a
downstream-module consequence of the intended err/errmsg semantics
change on excess positional arguments, not a mod-utils defect; needs
a follow-up fix in mod-fontawesome. New WARN lines (~55 distinct
patterns) are the intended warnings-first surface: mostly empty-
string values now flagged where legacy silently ignored them, and
newly-visible nested unsupported-attribute/type findings across
card, image, table, testimonials, hero, and sidebar components.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coordinator adjudication overruled the planned err-flip on excess positional arguments: the spec's acceptance criterion requires zero new errors through the shim path on the Hinode exampleSite, and warnings-first governs any newly-enforced failure class. Legacy surfaced the "unsupported argument at index %d" message but never gated on it (err stayed false, mapped args stayed populated), so enforcing it now would be a behavioral break. The positional-mapping loop in Args.html appends the finding to newmsg instead of errmsg: strict mode promotes it to a real error for clean-API callers, while the InitArgs shim keeps err false with args populated and the message in warnmsg — legacy-faithful except the message slice, resolving legacy's self-contradictory errmsg-with-err:false shape. Golden delta is confined to positional/excess-position-errors: the initargs half reverts to err false with name/kind populated and the message in warnmsg; the strict args half keeps err true. Hinode exampleSite smoke test now builds with zero ERROR lines: the 45 mod-fontawesome fas/fab/icon errors are gone (23 became warnings with unchanged text, the 21 secondary "Missing icon name" failures vanished since icon args stay populated). The distinct warning-message set is identical to the previous run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add an inittypes golden group covering scalar, nested-UDT, bookshop-blueprint, and child-merge structures, exercised through the current legacy InitTypes implementation before it is rewired as a shim over ArgsSchema. No fixture crashes the legacy path (the union-typed 'title' field resolves via Hugo's append-flattening of slice.type into aliases, so no map is indexed by a slice key). Two legacy quirks are captured verbatim for later reconciliation: bookshop blueprint dict values (e.g. heading) leak their raw instance keys into the top-level type def, and CloudCannon 'editors' metadata leaks through on nested UDT members that declare it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the 156-line legacy layouts/_partials/utilities/InitTypes.html with
the ~50-line shim over ArgsSchema.html, reconstructing the legacy
{types, udt, err, errmsg, warnmsg} contract from schema nodes. types values
use the node's declared type list, collapsing to a plain string when there is
exactly one (matches assets/args.html's `index $types $val.type` lookup);
udt is keyed by top-level UDT type name only, carrying `_reflect` computed
from node.kind (matches legacy's printf "%T" values for dict vs list UDTs).
Every changed inittypes golden line falls into one of four reconciled
categories:
- bookshop blueprint dict values (e.g. heading) no longer leak their raw
instance keys (align, title) into the top-level type def — a legacy
quirk from merging the raw blueprint value into the def; assets/args.html
never reads these extra keys, so this is inert to the verified consumer.
- CloudCannon 'editors' metadata no longer leaks through on nested UDT
members that declare it, for the same reason (never read by args.html).
- show_more (bookshop-component) gains a "default": false it previously
lost — this is the sidecar-default fix already characterized in
ArgsSchema.html (Task 6).
- ratio (child-merge) loses its inherited "options.values" — the child
structure overrides the global argument's type (select -> string), so
the type-collision clean-slate merge (Task 8 fix #4) correctly drops the
now-inapplicable enum constraint.
Verified udt.heading._reflect == "map[string]interface {}" and
udt.locations._reflect == "[]interface {}" in the nested-structure case.
Hinode exampleSite smoke test (HUGO_MODULE_REPLACEMENTS pointing at this
checkout): build succeeds, exit 0, zero ERROR lines. Raw WARN line counts
and per-page attributions are non-deterministic build-cache/parallelism
noise (confirmed by running the unchanged shim twice and observing
different counts/attributions both times); the set of 87 distinct warning
message templates is byte-identical across the pre-shim baseline and two
post-shim runs. No warning or error mentions args.html.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Switched ArgsSchema.html invocations to partialCached in Args.html, InitArgs.html, and InitTypes.html. Hinode exampleSite --templateMetrics: ArgsSchema.html cumulative: 2m5.18s → 0.88s (142x faster) Combined (3 partials): 6m25.82s → 1m9.12s (5.58x faster) Test result: golden check passed (12 groups) — zero behavior drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a README "Argument validation" section covering the Args.html signature/envelope, the ArgsSchema.html node contract, the InitArgs/ InitTypes shim migration note, the null-vs-explicit tolerance rule, and the warnings-first classes slated for promotion to errors after one release cycle. Mark the design spec Implemented (phases 0-4) and record the deviation log discovered during execution: the single-return-per- define restriction, element-wise message appends, reflect-type passthrough, the untestable cycle fixture, the excess-positional adjudication, the four core fixes from the Hinode smoke test, and the two legacy-behavior corrections (dict alias, false/0 defaults). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bumps the Go module path to /v6 and documents the v5-to-v6 migration (import paths, CloudCannon expose globs, behavior changes). The warning-to-error promotion is now planned as v7. BREAKING CHANGE: although the InitArgs/InitTypes API is drop-in compatible, validation behavior changes: nested and falsy defaults now apply (rendered output can change), null members are dropped from nested maps, all validation problems are reported per call, explicit falsy site parameters override static defaults, and newly detectable problem classes surface as warnings. Consumers must update the import path to github.com/gethinode/mod-utils/v6 and, when using setup-cloudcannon-cms, refresh expose globs that reference the vendored v5 path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows CI checks out the golden files with CRLF conversion while Hugo emits LF, so byte comparison failed on windows-latest only. Pin tests/golden/*.json to LF via .gitattributes and normalize line endings in golden.mjs as a defensive second layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…feat/args-system-redesign
Collaborator
Author
|
🎉 This PR is included in version 6.0.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This was referenced Jul 12, 2026
This was referenced Jul 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
This is a breaking release: mod-utils v6 (module path
github.com/gethinode/mod-utils/v6).Although the
InitArgs/InitTypesAPI is drop-in compatible, validation behavior changes inways that can affect rendered output and build logs (nested and falsy defaults now apply, null
members are dropped from nested maps, all problems reported per call, new warnings surface).
Consumers update the import path in
go.modandhugo.toml; sites usingsetup-cloudcannon-cmsmust also refresh expose globs referencing the vendoredv5path.See README "Migrating from v5 to v6". Hinode should absorb this in its own next major
(naturally Hinode v2). The warning-to-error promotion is planned as v7.
Summary
Redesigns mod-utils' argument/type validation system (spec:
docs/superpowers/specs/2026-07-12-args-type-system-redesign-design.md,status now Implemented (phases 0-4)). Two concerns that were previously interleaved in the
270-line
InitArgs.htmlare now separated: a cached schema compiler (ArgsSchema.html) and arecursive validator (
Args.html).InitArgs.html/InitTypes.htmlbecome thin compatibilityshims so all ~160 existing call sites keep working unchanged, with newly detectable problems
surfacing as warnings first.
Built with a characterization-first golden-file harness (12 test groups in
exampleSite) soevery behavior change — bug fix or otherwise — is visible as an explicit, reviewed diff in its
own commit.
Six defects fixed (design spec §1.1, items 1-6)
detection now recurse to arbitrary depth (UDT → UDT → scalar, lists of dicts).
false/0/""are now type-checked.or-based config fallthrough — an explicitfalse/0site parameter is now honoredinstead of silently falling back to the static default.
Args.html's envelope separates user values(
args) from bookkeeping (err/errmsg/warnmsg/defaulted); an argument nameddefaultor
errcan no longer collide.Warnings-first strictness rollout
Newly detectable problem classes ship as warnings through the
InitArgs.html/InitTypes.htmlshims for one full v5 release cycle before promotion to errors:
Args.htmlcalled directly withstrict: true(default) already treats all four as errorstoday.
CloudCannon compatibility preserved
Bookshop golden cases pin CloudCannon's editor-shaped payloads (every blueprint key present,
typically
null, plus_bookshop_name/_ordinal, snake_case keys) aserr: falseat everynesting level, distinct from genuinely-wrong explicit values, which still validate. No YAML
schema, blueprint, or external call-site changes anywhere in the ecosystem.
Metrics
ArgsSchema.htmlcumulative time on the Hinode exampleSite build (--templateMetrics), afterenabling
partialCachedkeyed by(structure, bookshop, child):~84.5k
ArgsSchema.htmlinvocations, ~100% cache hits after warmup.Follow-up (deferred, spec phase 5)
Args.htmldirectly.args.mddocs shortcode consumesArgsSchema.htmlinstead of re-deriving types.no signal for the integration smoke test).
fas/fab/iconshortcodes (stop forwarding excesspositions they already handle) before the promotion-to-errors release.
🤖 Generated with Claude Code