Skip to content

feat(catalogue): rebuild the form/composer pattern on Form + Field - #487

Merged
interacsean merged 3 commits into
mainfrom
feat/catalogue/composer-in-form
Sep 17, 2026
Merged

interacsean merged 3 commits into
mainfrom
feat/catalogue/composer-in-form

Conversation

@interacsean

Copy link
Copy Markdown
Contributor

Summary

form/composer (added in #479) is the only form/* pattern that uses neither Form nor Field — a bare Card of controls with an onClick submit. Its requiredImports were [Card, Textarea, Button, Checkbox] while all four siblings build on Form + Field + onFormSubmit.

That inconsistency matters for the shipped skill: an agent asked to build a form reads form/*, gets five patterns, and four say one thing while the fifth quietly does the opposite. It also left three real gaps.

What changed

A server rejection had nowhere to go. The body had no field identity, so a moderation block, rate limit, or closed thread could only surface as a toast — away from the text the user still has in the box. It now routes through Form's errors prop into Field.Error.

The Textarea was named by aria-label. Now a sr-only Field.Label. A real label, and it keeps the description/error wiring available rather than closing that door.

Send was an onClick handler. Now type="submit", so Form gates it on validation. Discard is explicitly type="button" — inside a <Form> an untyped button defaults to submit, which would have made Discard submit the composer.

Fixes a data-loss path. The reference implementation cleared the body unconditionally, discarding the user's text when a submit failed. It now clears only on success.

What deliberately did not change

The body stays controlled. It's read during render to gate Send and to swap the placeholder:

disabled={body.trim().length === 0 || submitting}
placeholder={internal ? "Internal context for teammates…" : "Reply to customer…"}

onFormSubmit hands you values at submit time and can't drive render, so useState here is load-bearing — not the mirroring anti-pattern the other form/* docs warn about. The pattern doc now states that explicitly, so the difference reads as intentional rather than as drift.

The visual design, action-row rules, and every do/dont in the pattern are untouched.

Verification

Behaviour was checked against the real components before writing, not assumed:

  • controlled Textarea in Field.Root keeps its submit gating
  • onFormSubmit receives { body: "…" }
  • required blocks submission and renders Field.Error
  • the sr-only Field.Label resolves as the control's accessible name (getByRole("textbox", { name: "Reply" }))
  • Discard clears

pnpm type-check, pnpm lint, catalogue type-check and check-generated-skills all pass.

Related

Branched from main, independent of #469 — it uses only Form, Field, and Textarea as they exist today. #469 separately corrects the form guidance in components.md and the four CRUD patterns; this removes the last inconsistency between that guidance and form/*.

Raised out of review discussion on #469, where the composer's divergence surfaced as an apparent contradiction in the shipped skill.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Code Metrics Report

main (35fcca2) #487 (e913373) +/-
Coverage 87.4% 87.4% 0.0%
Test Execution Time 2m2s 2m4s +2s
Details
  |                     | main (35fcca2) | #487 (e913373) | +/-  |
  |---------------------|----------------|----------------|------|
  | Coverage            |          87.4% |          87.4% | 0.0% |
  |   Files             |            204 |            204 |    0 |
  |   Lines             |           6014 |           6014 |    0 |
  |   Covered           |           5257 |           5257 |    0 |
- | Test Execution Time |           2m2s |           2m4s |  +2s |

Reported by octocov

@interacsean
interacsean marked this pull request as ready for review September 2, 2026 05:41
@interacsean
interacsean requested a review from a team as a code owner September 2, 2026 05:41
@interacsean
interacsean marked this pull request as draft September 2, 2026 05:41
@interacsean
interacsean marked this pull request as ready for review September 16, 2026 07:48
`form/composer` was the only `form/*` pattern using neither `Form` nor
`Field` — a bare `Card` of controls with an `onClick` submit. That made it
inconsistent with its four siblings and left three gaps.

- The body had no field identity, so a server rejection (moderation, rate
  limit, closed thread) had nowhere to land except a toast, away from the
  text the user still has. It now routes through `Form`'s `errors` prop
  into `Field.Error`.
- The `Textarea` was named by `aria-label`. It now uses a `sr-only`
  `Field.Label` — a real label, and it keeps the description/error wiring
  available for the next requirement.
- Send was an `onClick` handler. It is now `type="submit"`, so `Form` gates
  it on validation; Discard is explicitly `type="button"`, since inside a
  `<Form>` an untyped button defaults to submit.

The body stays controlled, and the pattern doc now explains why rather than
leaving it to look like the mirroring anti-pattern: it is read during render
to gate Send and to swap the placeholder, neither of which a submit-time
handler can do. This is the one `form/*` pattern where field state is
load-bearing.

Also fixes a data-loss path in the reference implementation — it cleared the
body unconditionally, discarding the user's text when a submit failed. It now
clears only on success.

Verified against the real components: the controlled `Textarea` inside
`Field.Root` keeps its submit gating, `onFormSubmit` receives the body,
`required` blocks submission and renders `Field.Error`, and the `sr-only`
label resolves as the control's accessible name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@interacsean
interacsean force-pushed the feat/catalogue/composer-in-form branch from c64ac03 to c64c917 Compare September 16, 2026 10:27
@interacsean

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Code Review failed. Please review the logs for details.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Generated by Code Review for #487

@interacsean

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Code Review completed successfully!

Completed package review for PR #487. The diff only changes catalogue reference content and a changeset; there are no in-scope changes under packages/** or changed package entrypoints/exports to flag, so no GitHub review comment was needed.

Generated by Code Review for #487

@interacsean
interacsean merged commit 48f9f90 into main Sep 17, 2026
5 checks passed
@interacsean
interacsean deleted the feat/catalogue/composer-in-form branch September 17, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants