feat(types): export ComboboxOption from the @object-ui/types root entry - #8177
Conversation
objectui#7691 made `@object-ui/types` the single AUTHORITY for
`ComboboxOption` — `@object-ui/components` stopped declaring its own copy and
now re-exports this one — but reached it only through the
`@object-ui/types/form` subpath, because the root barrel was held by
objectui#7683 at the time. Measured on `a4611b3e2`, the name read 0 hits on
`packages/types/src/index.ts` while `SelectOption` (2), `RadioOption` (1) and
`ComboboxSchema` (1) all read non-zero under the same word-boundary query, so
the zero was a reading rather than a dark instrument, and
`import type { ComboboxOption } from '@object-ui/types'` read TS2305.
Add the name to the barrel's existing named re-export list from `./form.js`,
next to `ComboboxSchema` the way `SelectSchema`/`SelectOption` and
`RadioGroupSchema`/`RadioOption` already sit. Additive only: the list stays an
explicit named list (never a wildcard, which would publish every other name in
`form.ts` as a side effect), the declaration stays in `form.ts`, the three
declared members are unchanged, and the `/form` subpath keeps working.
The new pin asserts BOTH spellings, because having put the name on the barrel
the most likely later "tidy" is to drop the subpath as redundant: the type
level pins each declared member through each entry point plus their invariant
equality (one declaration, not two forks), the source scan pins the explicit
named list and the declaration's location, and the manifest pin keeps
`exports["./form"]` pointing at the built subpath entry. The source scan is
what runs under `pnpm test`, which has no build step ahead of it, so nothing
here reads `dist/`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract review — PASS. Clause-② cleared. Held only on CI convergence.Reviewed by the PM seat (session Clause-② binds — this widens the published root entry surface of
So the widening is one additive name onto an existing explicit list, with no reachable collision. The part that actually earns the PASS is the Two further things worth recording:
Also correct, and easy to have got wrong: Not flipped ready yet — CI is still converging (10 jobs Generated by Claude Code |
Fixes #7697
One line on a barrel, one pin, one
minorchangeset.ComboboxOptionjoins the@object-ui/typesroot barrel's existing named re-exportlist from
./form.js, next toComboboxSchema— the waySelectSchema/SelectOptionand
RadioGroupSchema/RadioOptionalready sit on that same list.Premise, re-measured at the branch point
a4611b3e2The same word-boundary grep on
packages/types/src/index.ts:ComboboxOptionSelectOption(control)RadioOption(control)ComboboxSchema(control)The instrument is lit — three controls read non-zero under the identical query — so the
zero is a reading, not a dark instrument. The declaration sits at
form.ts:1409and thenamed list ended at
index.ts:184(both line numbers had drifted from the card's).The list is an explicit named list, not a wildcard that happens to miss the name.
Why it was missing — and why this is ⛔ not a defect in objectui#7691
objectui#7691 made this package the single authority for the name:
@object-ui/componentsstopped declaring its own copy and now re-exports this one(
packages/components/src/custom/combobox.tsximports and re-exports it from@object-ui/types/form). It reached that authority through the/formsubpath becausethe root barrel was held by objectui#7683 at the time, and a barrel line would have been
both a fence breach and a second published-surface addition. That review judged the
subpath sound on its own merits — it is a house pattern, alongside
@object-ui/types/zodand
@object-ui/types/internal/retired-field-keys. This PR is the follow-upobjectui#7691 could not take, not a correction of it.
The consequence a consumer saw:
import type { ComboboxOption } from '@object-ui/types'read
TS2305, while its two sibling option types on the same list were reachable.Additive only
Nothing removed, retyped or narrowed. The declaration stays in
form.ts(⛔ not moved),the list stays an explicit named list (⛔ not converted to a wildcard — that would publish
every other name in
form.tsas a side effect, a far wider surface change than this cardauthorises), the three declared members
value/label/disabled?are unchanged, andthe
@object-ui/types/formspelling keeps working. Both spellings now name onedeclaration.
Two facts that bound the blast radius, measured across
packages/,apps/andexamples/: there is exactly one declaration ofComboboxOptionin the repository(
packages/types/src/form.ts:1409), and there are zero wildcard re-exports of@object-ui/typesanywhere. So a name added to this barrel cannot collide with, shadow,or ambiguate anything downstream.
Both spellings are pinned, on purpose
packages/types/src/__tests__/combobox-option-root-barrel-7697.test.ts. Having put thename on the barrel, the most likely later "tidy" is to drop the subpath as redundant —
pinning only the new spelling would let that through green. Three independent layers:
equality (
Eq, the house spelling), so a root line that published a differentComboboxOptionwould still red. Enforced bytsc -p tsconfig.test.json, the third legof this package's
type-checkscript, which CI runs as its own job../form.jsclause; the clause is still an explicitnamed list; the declaration is still in
form.tsand is not inindex.ts. This is thehalf that runs under
pnpm test, and it deliberately reads source, neverdist/:this repo's per-PR
testjob runs with no build step ahead of it, so a test needing afresh
dist/would be vacuously absent-or-red on a cold cache(
package-exports-manifest.test.tsrecords that same constraint for this package).exports["./form"]still points at the built subpath entry. Only thislayer proves the spelling
@object-ui/types/formstays resolvable to a consumer.Every layer carries its own control (
SelectOption,RadioOption,ComboboxSchema, andthe root export entry), so a dark instrument reads as a failure rather than as a pass.
Ablation — the pin is lit, and the asymmetry is the whole point
Run on the committed tree, mutation proven on disk (exact-line hits 1 → 0 plus the
unified diff), restore proven by state (blob hash
67b5bfc65…identical to the HEADblob,
git diff HEADempty, hits back to 1) under atrap … EXIT INT TERMwith absolutepaths — never by an exit code.
With the barrel line removed:
plus four TS2322 from the
Eqassertions that lost their type — and zero errors naming'../form'. Under vitest, exactly one test went red (the source scan) with the other ninegreen, the
/formand manifest layers among them. That asymmetry — the root spellingfails, the subpath spelling does not — is what makes the pin meaningful rather than
decorative.
Verification
pnpm --filter @object-ui/types type-checktsclegs)pnpm exec vitest run packages/types/ scripts/npx eslint --format jsonon the 2 changed source filesnode scripts/check-changeset-presence.mjsnode scripts/check-control-bytes.mjsscripts/is in scope deliberately, not justpackages/types/— it holds the tree-levelgates (exported-name authority, barrel/export coverage, changeset presence, dist
completeness) that a diff adding an exported name to a root barrel is squarely in reach of.
one-authority-per-exported-name-6273is the relevant one and it stays green: a plainexport type { X } from './x'is one declaration with many export sites, which is exactlywhat this line is.
Declared narrowing.
turbo ls --affectednames essentially the whole monorepo, becauseeverything depends on
@object-ui/types. The full farm is CI's run, not this seat's. Thenarrowing rests on the two measurements above (one declaration site, zero wildcard
re-exporters) plus the shape of the change: adding a name to an export list can only ever
add a resolvable name. Repo-level
eslintis likewise CI's; the targeted run above isnon-excluding because this repo configures no type-aware linting (no
project:/projectServiceineslint.config.js), so this diff cannot move the verdict on any fileit did not touch.
The dependency-closure build (
pnpm --filter '@object-ui/types^...' build) is vacuoushere and was confirmed so rather than assumed: it exits with
None of the selected packages has a "build" script—@object-ui/typeshas no workspaceruntime dependency, only the external
@objectstack/specandzod.Read-couplings checked and deliberately left alone
content/docs/components/form/combobox.mdx— read, not edited. It already documentsComboboxOptionwith thedisabled?: booleanrow (2 hits on the page) sinceobjectui#7687 landed as PR fix(components): combobox honours options[].disabled #8142; the census pin
component-docs-disabled-inherited-7239.test.tsnow claims that row, and this diff doesnot touch the page, so the pin does not move. It is green in the run above.
Session:
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code