Flesh out the PRD §12.2 config shape so phase-2 rules (color/palette,
radius/scale, edge/near-alignment, a11y/touch-target) can pull their
thresholds from typed config rather than hard-coded constants.
Changes:
- ColorSpec: keep flat slash-namespaced tokens (e.g. "bg/canvas") with
delta_e_tolerance default 2.0. The flat shape is documented as the
canonical form; nested-table support deferred per ticket guidance.
- RadiusSpec: rename allowed_px → scale for consistency with
spacing.scale and type.scale. The old name is rejected (no alias)
since no shipped rule consumes it yet.
- AlignmentSpec: add tolerance_px (default 3) for edge-cluster window.
- A11ySpec: add nested touch_target (TouchTargetSpec) defaulting to
24×24 CSS px per WCAG 2.5.8.
deny_unknown_fields stays on every nested struct. Schema regenerated
via `cargo xtask schema`. Round-trip tests cover both populated and
default forms; old aliases continue to be rejected.
Fixes #21
Target branch
mainSpec
Fixes #21
PRD §12.2. Phase 2 batch 2A. Sets the config shape that the phase-2 rules (color/palette-conformance #23, radius/scale-conformance #24, edge/near-alignment #27, a11y/touch-target #26) will read from.
Summary
[color],[radius],[alignment],[a11y]— every nested struct keepsdeny_unknown_fields.alignment.tolerance_px(default 3) anda11y.touch_target.{min_width_px, min_height_px}(default 24×24 per WCAG 2.5.8).color.delta_e_tolerancedefault settles at 2.0 (the rule itself can override at consume time).radius.allowed_px→radius.scaleto matchspacing.scaleandtype.scale. Schema, example, and tests updated; the old name is rejected.Crates touched
plumb-coreplumb-formatplumb-cdpplumb-configplumb-mcpplumb-clixtaskdocs/.agents/or.claude/.github/System impact
# Errorssection if fallible)tools/listentry + protocol test)register_builtinentry)cargo xtask schema+ commit result).agents/rules/determinism.md)Architectural compliance
plumb-corehas no internal deps; unsafe only inplumb-cdp;println!/eprintln!only inplumb-cli.thiserror-derived in libs;anyhowonly inplumb-cli::main.unwrap/expect/panic!in library crates.SystemTime::now/Instant::nowinplumb-core.HashMapin observable output paths (useIndexMap).#[allow(...)]is local and has a one-line rationale.Test plan
just validatepasses locallycargo xtask pre-releasepasses (if rule or schema changed)just determinism-checkpasses (3× byte-diff clean)cargo deny checkpassesRound-trip tests (
crates/plumb-config/tests/load_example.rs):loads_prd_color_radius_alignment_a11y_sectionspopulates every new field and asserts the parsed values.defaults_for_color_radius_alignment_a11y_sectionswrites an empty config and asserts every default (includingdelta_e_tolerance = 2.0,tolerance_px = 3, touch-target24×24).rejects_old_config_aliases_and_unknown_fieldsextended with[radius] allowed_px, plus unknown fields underalignment,a11y,a11y.touch_target, andcolor.schema_uses_prd_names_and_drops_old_aliasesextended to assertRadiusSpec.scale,AlignmentSpec.tolerance_px,A11ySpec.touch_target, andTouchTargetSpec.{min_width_px, min_height_px}.Documentation
# Errorssection on every new public fallible fn (no new fallible APIs)docs/src/updated when user-visible behavior changeddocs/src/rules/<category>-<id>.mdwritten for new rules (n/a)docs/src/configuration.mdadds a per-section reference covering defaults and the slash-namespacing convention. Prose kept short; no AI-tell vocabulary.Breaking change?
The pre-release
radius.allowed_pxfield is renamed toradius.scale. Anyone on aplumb.tomlfrom before this PR replaces:with:
The old name is rejected (
deny_unknown_fields), so the failure surface is loud. No shipped rule consumedradius.*yet, so no rule-side compat shim is needed.Checklist
codex/<primary>-<type>-<slug>/gh-review --local-diff main...HEADrun locallyReviewer notes
Decisions worth flagging:
paletteform alongside the flat one. I kept the flat slash-namespaced shape ("bg/canvas" = "#...") and documented it as canonical — clarity > scope creep. If a nested form turns out to be ergonomic in dogfooding, it's a clean follow-up.radius.allowed_px→radius.scale(no alias). The pre-release schema is unstable, no shipped rule readsradius.*yet, so a hard rename is cheaper than a long-lived#[serde(alias = "allowed_px")]. The CHANGELOG flags it underChanged.delta_e_tolerancedefault stays 2.0. The phase-2 runbook for feat(core): rule color/palette-conformance (Oklab ΔE00) #23 mentions 1.5; the pre-existing default is 2.0 and feat(core): rule color/palette-conformance (Oklab ΔE00) #23 can override at the rule-default level if the spec confirms 1.5. Leaving it as-is keeps this PR scoped to schema shape, not threshold tuning.