Skip to content

Implement #117 field error-presentation seam: Default errors={ValidationError[]} #129

Description

@timkindberg

Question

Land the library half of the seam locked in #117: recipes inject per-field errors via <Default of={field} errors={ValidationError[]} /> (prop, recipe-pre-gated) and reuse FormFrame’s accessible field composition — instead of each recipe rebuilding FieldShell / FieldErrors / a11y.

Play after the merged form-lib recipes (PR #127 / #123+#124). Those recipes prove BYO errors work; they do not yet exercise this inject path (DefaultFieldRoot still reads ValidationProvider / the store).

Recommended model

Sonnet 5 — the interface is locked; this is careful React/renderer surgery + thinning the example recipes onto it. Escalate only if the renderNodeRules / parts re-sourcing gets subtle.

Locked contract (#117 — do not reopen)

  • Injected shape: errors: ValidationError[], per field, keyed by node.path, pre-gated by the recipe (present == show). No show flag.
  • Mechanism: a prop on ADR-017 re-entry — <Default of={fieldNode} errors={…} />. Not a new context-shaped public seam. renderNodeRules handlers own errors in lexical scope and thread them into parts; any “set once” ergonomic is YAGNI until earned.
  • a11y (KEEP): control gets aria-invalid + aria-describedby={fieldErrorId(path)}; error list gets id={fieldErrorId(path)}. No role="alert" by default (today’s DefaultFieldErrors still has it — drop it as part of this work).
  • KEEP: default field composition; movable Errors / Control parts re-sourced from the injected errors; ValidationError + groupErrorsByPath; fieldControlId / fieldErrorId.
  • Still recipe / later Cleanup: drop validation-* packages, slim useFormTree, finalize ADRs #126: store-backed hooks, ValidationProvider, status hooks, submit-gating — do not re-entrench them. This ticket only adds the inject path and prefers it when errors is passed.

Acceptance

  1. Default / field root accepts optional errors?: ValidationError[]. When provided, display + a11y are driven from that prop (not the store). When omitted, current store-backed behavior remains until Cleanup: drop validation-* packages, slim useFormTree, finalize ADRs #126 demotes it (dual path is OK short-term).
  2. Library default field errors: no role="alert".
  3. Example recipes (fieldPresentation.recipe.tsx + RHF/TanStack controls, or a thin follow-up commit) use the inject path and delete duplicated shell/a11y where the library now covers it — keep only form-lib-specific control wiring + error-shape → ValidationError[] (or message) normalization.
  4. Unit/browser tests cover: inject shows errors; a11y attrs track injected errors; omit-prop still works with provider/store until demotion.
  5. npm run gate green.

Out of scope

Relationships

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions