Skip to content

fix(field): warn when Field.Control renders a self-framed control - #629

Merged
interacsean merged 9 commits into
mainfrom
fix/1977-field-control-self-framed-warning
Oct 23, 2026
Merged

interacsean merged 9 commits into
mainfrom
fix/1977-field-control-self-framed-warning

Conversation

@interacsean

@interacsean interacsean commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes tailor-inc/platform-planning#1977

Summary

Writing <Field.Control render={<Select />} /> draws a second frame around Select. Field.Control merges its input styling and <input> props onto whatever it renders, and Select already draws its own trigger and connects to Field.Root by itself. It type-checks and lints, so the double frame was the only symptom.

This PR covers options 1 and 2 from the issue (docs + a console warning):

  • Warning. Field.Control now logs a console warning, once per mounted instance, when render is one of these controls: Select, Combobox, Autocomplete (each with .Async), Checkbox, Textarea, DateField, DatePicker, DateRangePicker. The message names the control and says to put it directly under Field.Root. It logs in every build, not just in development: the package doesn't reference process, and import.meta.env.DEV would be baked in when the library is built. It only fires for UI that is already broken. Only the direct element form is detected; a wrapper component or the function form of render isn't. Input is left out on purpose: render={<Input />} merges identical classes, so it doesn't double-frame, and a warning would be a false positive.
  • Docs. form gets a table of which controls go under Field.Control and which go directly under Field.Root. Those docs also call out Input as the exception: it doesn't connect to Field.Root, so Field.Control takes its place inside a field. select gets an "inside a form" note. The existing form-modal rule is widened and gains an anti-pattern line.
  • Refactor (separate commit). The warn-once dev guard that ai-chat and filter-rail each wrote by hand is now one internal hook, hooks/use-once-warnings.ts. It is not exported.

Option 3 from the issue (support the composition with a single frame) is left out. Field.Control also passes <input> props to the rendered element, so making it look right would hide a wrong wiring. The wider inconsistency (Input is the only AppShell control that doesn't connect to Field.Root) is being written up separately as a decision proposal.

Verification

  • Tested on a temporary vite-app showcase page: render={<Select />} shows the double frame and logs the warning once. A Select directly under Field.Root renders a single frame.
  • Local runs: fmt:check, type-check, lint, test (4 new Field tests), docs:check, core build and check-dts.

🤖 Generated with Claude Code

interacsean and others added 2 commits October 21, 2026 11:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Passing Select, Combobox, Autocomplete, Checkbox, Textarea or the date
controls to Field.Control's render draws a second frame. Warn in the
console and document which controls go directly under Field.Root.

Refs tailor-inc/platform-planning#1977

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@interacsean

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Code Review has started processing this issue comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Code Metrics Report

main (eb44189) #629 (562afd6) +/-
Coverage 89.3% 89.3% +0.0%
Test Execution Time 2m32s 1m46s -46s
Details
  |                     | main (eb44189) | #629 (562afd6) |  +/-  |
  |---------------------|----------------|----------------|-------|
+ | Coverage            |          89.3% |          89.3% | +0.0% |
  |   Files             |            227 |            228 |    +1 |
  |   Lines             |           7240 |           7241 |    +1 |
+ |   Covered           |           6470 |           6471 |    +1 |
+ | Test Execution Time |          2m32s |          1m46s |  -46s |

Code coverage of files in pull request scope (96.2% → 96.3%, patch 100.0%)

Files Coverage +/- Patch Coverage Status
packages/core/src/components/ai-chat/ai-chat.tsx 91.6% -1.7% - modified
packages/core/src/components/field/field.tsx 100.0% 0.0% 100.0% modified
packages/core/src/components/filter-rail/filter-rail.tsx 96.5% -0.2% 100.0% modified
packages/core/src/hooks/use-once-warnings.ts 100.0% +100.0% 100.0% added

Reported by octocov

Comment thread docs-src/components/select.docs.outline.md Outdated
Comment thread packages/core/src/components/field/field.tsx
interacsean and others added 2 commits October 21, 2026 13:35
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the central list in field.tsx, so a new form control picks up the
Field.Control warning by copying its neighbours. Adds the step to the
composite-field-controls review reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@interacsean
interacsean marked this pull request as ready for review October 21, 2026 02:49
@interacsean
interacsean requested a review from a team as a code owner October 21, 2026 02:49
@IzumiSy
IzumiSy requested a balanced review from Copilot October 22, 2026 04:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Shared registry mutations retain otherwise-unused controls and dependencies in consumer bundles, undermining tree shaking.

1 open issue
What changed in this PR

This PR adds warnings and composition guidance for AppShell controls that already draw their own frame and connect to Field.Root. It diagnoses incorrect Field.Control composition rather than changing its rendering behavior.

Changes:

  • Adds a control registry, misuse warnings, and four warning tests.
  • Shares the existing warn-once hook across Field, AIChat, and FilterRail.
  • Updates documentation sources, generated guidance, and release notes.
File Description
packages/​core/​src/​lib/​self-wired-controls.ts Adds the control-name registry.
packages/​core/​src/​hooks/​use-once-warnings.ts Extracts shared warning deduplication.
packages/​core/​src/​components/​textarea/​textarea.tsx Registers Textarea.
packages/​core/​src/​components/​select/​select-standalone.tsx Registers Select and Select.Async.
packages/​core/​src/​components/​filter-rail/​filter-rail.tsx Uses the shared warning hook.
packages/​core/​src/​components/​field/​field.tsx Detects unsupported render composition and warns.
packages/​core/​src/​components/​field/​field.test.tsx Tests warning detection and deduplication.
packages/​core/​src/​components/​date-field/​date-range-picker.tsx Registers DateRangePicker.
packages/​core/​src/​components/​date-field/​date-field.tsx Registers DateField and DatePicker.
packages/​core/​src/​components/​combobox/​combobox-standalone.tsx Registers Combobox and Combobox.Async.
packages/​core/​src/​components/​checkbox/​checkbox.tsx Registers Checkbox.
packages/​core/​src/​components/​autocomplete/​autocomplete-standalone.tsx Registers Autocomplete and Autocomplete.Async.
packages/​core/​src/​components/​ai-chat/​ai-chat.tsx Replaces its local warning hook.
docs/​patterns/​form-modal.md Updates generated composition guidance.
docs/​components/​select.md Adds generated form-placement guidance.
docs/​components/​form.md Adds generated control-placement guidance.
docs-src/​patterns/​form-modal.docs.outline.md Expands composition rules and anti-patterns.
docs-src/​components/​select.docs.outline.md Adds a Select field example.
docs-src/​components/​form.docs.outline.md Explains control placement and detection limits.
docs-manifest.json Updates documentation hashes.
.changeset/​field-control-render-warning.md Records the patch release behavior.
.agents/​references/​README.md Updates the field-control reference description.
.agents/​references/​composite-field-controls.md Documents control registration guidance.

Review effort: Balanced

Comment thread packages/core/src/lib/self-wired-controls.ts Outdated
markSelfWired now returns the component and wraps each definition with a
PURE annotation, so a bundle that drops a control drops its registration
too. A bare top-level call kept every registered control alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IzumiSy
IzumiSy previously approved these changes Oct 22, 2026
@IzumiSy
IzumiSy self-requested a review October 22, 2026 06:58
@IzumiSy
IzumiSy dismissed their stale review October 23, 2026 02:14

This PR needs update as we discussed on Slack

Reverts the per-component markSelfWired registry (b821991, b2f7b30). The
PURE-annotated wrappers have side effects, so marking them pure risks
bundlers dropping the registration. A central list is simpler; the
composite-field-controls reference now says new controls must be added to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@IzumiSy IzumiSy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good now 🙏

@interacsean
interacsean merged commit cc749d5 into main Oct 23, 2026
8 checks passed
@interacsean
interacsean deleted the fix/1977-field-control-self-framed-warning branch October 23, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants