--- name: project-form-patterns description: Form implementation guidance for the aghub desktop app. Use when building or refactoring forms in `crates/desktop/src`, especially HeroUI v3 + React forms, RHF integration, validation, custom editors, and error presentation. Triggers: MCP forms, settings dialogs, create/edit panels, `TextField`, `FieldError`, `Select`, `react-hook-form`, key-value editors, validation behavior. --- # aghub Form Patterns Follow these rules when building forms in this project. ## Source of Truth - Check the official docs first, not memory and not bundled/local docs, when form behavior is in question. - For HeroUI, prefer the official site: - `https://v3.heroui.com/docs/react/components/form` - `https://v3.heroui.com/docs/react/components/text-field` - `https://v3.heroui.com/docs/react/components/field-error` - `https://v3.heroui.com/docs/react/components/select` - Use local skill docs only as a convenience after confirming the official API. ## Default Stack - Prefer `react-hook-form` for non-trivial forms. - Use `Controller` for HeroUI `Select` and any custom controlled widgets. - Keep one form state. Do not create a second derived validation state unless there is a concrete need the form library cannot express. ## Validation Behavior - When RHF controls validation, set HeroUI form fields to `validationBehavior="aria"`. - Do not rely on HeroUI/native validation defaults together with RHF. Native validation can steal focus and submission flow while bypassing the error UI you expect from RHF. - Let RHF own validation rules and submission blocking. ## Error Rendering - For HeroUI text fields, use: - `isInvalid={Boolean(fieldState.error)}` - Conditionally render `{fieldState.error.message}` inside the same `TextField`. - Do not invent unsupported props. In particular, do not assume `TextField` supports an `errorMessage` prop. - Keep the official anatomy: ```tsx {fieldState.error && {fieldState.error.message}} ``` - For non-form or custom composite controls, prefer HeroUI `ErrorMessage` instead of hand-rolled error text. - Use `ErrorMessage` for collection-style or custom editors that are not true form fields, including key/value editors, tag selectors, and similar composite controls. ## Custom Editors - Custom widgets like `AgentSelector`, `EnvEditor`, `HttpHeaderEditor`, and `KeyPairEditor` should still be registered in RHF through `Controller`. - For custom editors that manage arrays or compound values, compute validation from the current field value and surface one aggregated error below the editor when possible. - For that aggregated error, prefer HeroUI `ErrorMessage`. - Do not inject per-row error UI into tight horizontal layouts unless the design explicitly calls for it. ## Key/Value Editors - Keep row layout simple: - two inputs - one delete button - no extra wrappers that change flex behavior unless necessary - Prefer aggregate error text below the whole editor over inline row errors. This avoids breaking spacing and alignment. - Implement that aggregate error with HeroUI `ErrorMessage`, not a custom `

` block. - If you must show row-level issues, redesign the layout first; do not bolt error blocks into a row that was designed as a single-line control. ## Practical Rules - Prefer `onPress` for HeroUI buttons. - Use `type="button"` for non-submit buttons inside forms. - Preserve existing visual patterns in this repo; do not restyle forms while adding validation. - After form changes, run `bun run build` in `crates/desktop` when possible and separate unrelated existing build failures from the changes you made. ## Anti-Patterns - Do not mix RHF validation with a parallel `validationErrors` state for the same fields. - Do not depend on HeroUI default native validation when you expect RHF errors to drive the UI. - Do not push validation messages into each key/value row unless you intentionally redesign that editor. - Do not trust remembered HeroUI APIs for forms without checking the official site first.