diff --git a/.agents/references/README.md b/.agents/references/README.md index 790336f6..c2fc3f13 100644 --- a/.agents/references/README.md +++ b/.agents/references/README.md @@ -10,7 +10,7 @@ Use these references from both implementation-time and review-time skills. Treat - [react-use-effect.md](react-use-effect.md) — state ownership, `useEffect`, async lifecycle, subscriptions, memoization - [accessibility.md](accessibility.md) — interactive semantics, keyboard support, focus, labels, announced state - [low-level-apis.md](low-level-apis.md) — timers, measurement, observers, imperative DOM, browser coordination -- [composite-field-controls.md](composite-field-controls.md) — field semantics, composite inputs, `Field` / `Form` bridging, proxy or hidden inputs, validation wiring, and alignment with nearby Base UI-backed control contracts +- [composite-field-controls.md](composite-field-controls.md) — field semantics, composite inputs, `Field` / `Form` bridging, proxy or hidden inputs, validation wiring, the `Field.Control` self-wired list, and alignment with nearby Base UI-backed control contracts ## Inclusion Criteria diff --git a/.agents/references/composite-field-controls.md b/.agents/references/composite-field-controls.md index e39f230d..ee813024 100644 --- a/.agents/references/composite-field-controls.md +++ b/.agents/references/composite-field-controls.md @@ -37,6 +37,10 @@ Review for consistency around: Be skeptical when a composite field control invents a parallel API unless the underlying semantics truly differ. +### Register as self-wired + +Every control that draws its own frame and connects to `Field.Root` by itself — composite or a thin Base UI wrapper — gets an entry in `selfWiredControls` in `components/field/field.tsx`, including compound members such as `.Async`. `Field.Control` uses that list to warn when the control is passed to its `render` prop, which draws a double frame. A control missing from the list fails silently: no warning, the same double frame. + ### One semantic value across all input paths Check whether these paths still converge on the same meaning when they exist: diff --git a/.changeset/field-control-render-warning.md b/.changeset/field-control-render-warning.md new file mode 100644 index 00000000..e9104146 --- /dev/null +++ b/.changeset/field-control-render-warning.md @@ -0,0 +1,5 @@ +--- +"@tailor-platform/app-shell": patch +--- + +`Field.Control` now logs a console warning when it is given an AppShell control that draws its own frame through `render`, such as `} />`. That composition renders a double border. The warning logs in every build, not only in development, because it only fires for UI that is already broken. `Select`, `Combobox`, `Autocomplete`, `Checkbox`, `Textarea`, `DateField`, `DatePicker`, and `DateRangePicker` go directly under `Field.Root` instead. The Form, Select, and form-modal docs now say which controls go where. diff --git a/docs-manifest.json b/docs-manifest.json index 0589330d..387f1956 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -699,9 +699,9 @@ "symbols": ["Form", "FormProps"], "hashes": { "typeSurface": "7b374d45d53d4535", - "outline": "79c9b0f08ca38027", + "outline": "7fefd291a00de910", "snapshot": "5d7a7904c5256f29", - "outputMd": "91d901b17aed7781", + "outputMd": "e4a66f7b1ee4aa98", "examples": null } }, @@ -733,9 +733,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "3301b263f6802090", + "outline": "a39589955fe582c8", "snapshot": null, - "outputMd": "9d068e568c9f3bdc", + "outputMd": "be00d1109761b7f1", "examples": "7836309f2df327ee" } }, @@ -1209,9 +1209,9 @@ "symbols": ["Select", "SelectAsyncFetcher"], "hashes": { "typeSurface": "429710973dfa912b", - "outline": "721e8995f53f0e8f", + "outline": "a8b7834a8817e0d5", "snapshot": "2ef5b7324018ff8f", - "outputMd": "ae287e2bb5abc1c6", + "outputMd": "b3dd3b9a58a9d290", "examples": "1ce89cec30045466" } }, @@ -1759,14 +1759,14 @@ "packages/core/skills/app-shell-patterns/references/components/dialog.md": "ec6a0ac098d1d87a", "packages/core/skills/app-shell-patterns/references/components/document-progress-card.md": "d0fb940f685f8869", "packages/core/skills/app-shell-patterns/references/components/filter-rail.md": "c6a48c4d8e8b0078", - "packages/core/skills/app-shell-patterns/references/components/form.md": "91d901b17aed7781", + "packages/core/skills/app-shell-patterns/references/components/form.md": "e4a66f7b1ee4aa98", "packages/core/skills/app-shell-patterns/references/components/global-header-layout.md": "dd2b0da3702cf8d9", "packages/core/skills/app-shell-patterns/references/components/grid.md": "9051eb53dde4179f", "packages/core/skills/app-shell-patterns/references/components/input.md": "a3a6c9679af99d37", "packages/core/skills/app-shell-patterns/references/components/layout.md": "66696139a46fabb0", "packages/core/skills/app-shell-patterns/references/components/menu.md": "ceb4a05822538d20", "packages/core/skills/app-shell-patterns/references/components/metric-card.md": "5a1c557768c6590f", - "packages/core/skills/app-shell-patterns/references/components/select.md": "ae287e2bb5abc1c6", + "packages/core/skills/app-shell-patterns/references/components/select.md": "b3dd3b9a58a9d290", "packages/core/skills/app-shell-patterns/references/components/sheet.md": "1f81ebd562c50371", "packages/core/skills/app-shell-patterns/references/components/sidebar-group.md": "a36192df64f16adc", "packages/core/skills/app-shell-patterns/references/components/sidebar-item.md": "9236c03e53aa79f2", @@ -1807,7 +1807,7 @@ "packages/core/skills/app-shell-patterns/references/api/use-time-zone.md": "30c15545ab3c4954", "packages/core/skills/app-shell-patterns/references/api/use-toast.md": "30983a761bc61593", "packages/core/skills/app-shell-patterns/references/patterns/form-composer.md": "ed187ac3a0773d3d", - "packages/core/skills/app-shell-patterns/references/patterns/form-modal.md": "9d068e568c9f3bdc", + "packages/core/skills/app-shell-patterns/references/patterns/form-modal.md": "be00d1109761b7f1", "packages/core/skills/app-shell-patterns/references/patterns/form-sectioned.md": "d31cd3ae6ad0790c", "packages/core/skills/app-shell-patterns/references/patterns/form-single-page.md": "10a0f4d91a120752", "packages/core/skills/app-shell-patterns/references/patterns/form-wizard.md": "851afb4672da6673", diff --git a/docs-src/components/form.docs.outline.md b/docs-src/components/form.docs.outline.md index 08958cbd..7e6f81d8 100644 --- a/docs-src/components/form.docs.outline.md +++ b/docs-src/components/form.docs.outline.md @@ -102,14 +102,14 @@ A compound component that groups all parts of a form field and manages its valid ### Sub-components -| Sub-component | Description | -| ------------------- | --------------------------------------------------------------------------------------------- | -| `Field.Root` | Context boundary for a single field. Manages validation state. | -| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context. | -| `Field.Control` | Styled ``. Can be omitted when using a Base UI-backed AppShell component as the input. | -| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`. | -| `Field.Error` | Validation error message. Use `match` to target specific validity states. | -| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI. | +| Sub-component | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `Field.Root` | Context boundary for a single field. Manages validation state. | +| `Field.Label` | Accessible label; `htmlFor` is resolved automatically via context. | +| `Field.Control` | Styled text ``. `Select`, `Combobox`, and the other self-wired controls go directly under `Field.Root` instead. | +| `Field.Description` | Supplementary help text; automatically linked via `aria-describedby`. | +| `Field.Error` | Validation error message. Use `match` to target specific validity states. | +| `Field.Validity` | Render-prop access to the field's `ValidityState` for fully custom validation UI. | ### Field.Root Props @@ -136,7 +136,17 @@ A compound component that groups all parts of a form field and manages its valid ### Using Another AppShell Component as the Control -`Field.Control` can be omitted when using a Base UI-backed AppShell component (e.g. `Select`, `Combobox`). The component registers itself with the `Field` context automatically, inheriting label association and validation state. +`Field.Control` is a text input. It is the control for plain text-like fields, and you can pass it a native element such as `render={