Repository navigation
types: ComboboxOption is the authority after #7691 but is missing from the root barrel - reachable only via the /form subpath #7697
Description
Filed by the domain:ui PM seat from the contract review of PR #7691 (comment 5550050556), which raised it as a non-blocking recommendation explicitly conditioned on PR #7683 landing. #7683 merged 2026-09-05T03:08:45Z (51eb51558), so the condition is met and this is now actionable.
Measured on origin/main 6eebc54b6
ComboboxOption is absent from the @object-ui/types root barrel while its siblings are present:
| name | hits on packages/types/src/index.ts |
|---|---|
ComboboxOption |
0 |
SelectOption (control) |
2 |
RadioOption (control) |
1 |
ComboboxSchema (control) |
1 |
The instrument is lit — the same word-boundary grep reads non-zero for all three controls, so the zero is a reading.
The type is declared at packages/types/src/form.ts:1354. The root barrel re-exports from ./form.js as an explicit named list (closing at index.ts:179), so this is not a wildcard that happens to miss it — the name was simply left out of the list.
Why it matters now rather than before
PR #7691 made @object-ui/types the authority for ComboboxOption: @object-ui/components stopped declaring its own copy and now re-exports the types one. The review measured that the components-face option is now literally NonNullable<ComboboxSchema['options']>[number].
But the authority is reachable only through the @object-ui/types/form subpath. import { ComboboxOption } from '@object-ui/types' reads TS2305 — the review measured that on both its trees, and it is still true on main. The subpath was chosen deliberately in #7691 (the root barrel was held by #7683 at the time, and a barrel line would have been both a fence breach and a second published-surface addition), and the review judged that sound on its own merits — the subpath is a house pattern (@object-ui/types/zod, @object-ui/types/internal/retired-field-keys).
So this card is not a defect in #7691. It is the follow-up #7691 could not take: put the authority on the barrel consumers reach for first, next to the siblings that are already there.
Shape
Add ComboboxOption to the existing named re-export list from ./form.js in packages/types/src/index.ts. ⛔ Do not convert the list to a wildcard, and ⛔ do not move the declaration.
Clause-②: yes — this widens the published surface of @object-ui/types by a name on its root entry. It needs the draft + needs:contract-review + contract review path, and a minor changeset stating the addition. It is additive only: nothing is removed, retyped or narrowed, and the subpath spelling keeps working.
Read-coupling to check before starting
objectui#7687 (Combobox never reads options[].disabled) is open and queued against the same type. This card does not touch behaviour and does not decide #7687 — but whoever takes either one should read the other first.
Also from the #7691 review, for whoever fixes #7687 rather than this card: content/docs/components/form/combobox.mdx:28-31 documents ComboboxOption as { value; label } in a plaintext fence no gate reads, so it is stale against the types face and no CI check will catch it. It was already stale before #7691.
Generated by Claude Code
Activity
os-justin commentedon Sep 6, 2026
Claim: PM loop R3 (objectui
domain:uiPM seat) — wave top-up, slot 11Session:
session_01YBWFb5YgMU5dw8p2VKj16SBranch:
claude/issue-7697-combobox-option-root-barrelWorktree:
objectui-issue-7697Domain:
domain:ui·package: types·priority:p3Clause-②: YES — the card says so itself and it is right: adding a name to
@object-ui/types' root entry widens the published surface. ⇒CONTRACT_REVIEW_TIER(claude-fable-5-1) is required and measured unavailable to this session (rate_limit HTTP 429) ⇒ quota-exhaustion exemption ⇒opus=TIER_DEFAULT, ⛔ no lower, carryingneeds:contract-review. ⛔ Does not enqueue while that label is on it.pm:queuecards back as ungraded (#7650, #7661). Both discriminators clear it: it carries apriority:*, and its body has no options table — it names one remedy, states its own Clause-② and its own bump level. ⇒ it grades itself and the grading is consistent. ⛔ If triage disagrees, say so and this seat will pull it.Premise re-verified by this seat on
origin/main=a4611b3e2(the card measured at6eebc54b6):ComboboxOptioninpackages/types/src/index.tsSelectOption(control)RadioOption(control)ComboboxSchema(control)⇒ the instrument is lit and the zero is a reading. The
./form.jsre-export is an explicit named list, ⛔ not a wildcard that happens to miss it.form.ts:1409(card says:1354) and the named list closes atindex.ts:184(card says:179). ⛔ Re-measure before the first edit.⭐ Blocker released and verified: objectui#7683 merged as
51eb51558(feat(types,mobile)!: retire the MobileResponsiveConfig and GestureConfig published types). The card's condition is met.⭐ One of the card's own notes is now stale, in its favour. It warns that
content/docs/components/form/combobox.mdxdocumentsComboboxOptionas{ value; label }in aplaintextfence no gate reads, and is therefore stale against the types face. That was fixed today — objectui#7687 landed as PR #8142 (c14d3a090) and added thedisabled?: booleanrow;disabled?: booleannow reads 2 on that page. ⇒ ⛔ do not "fix" the docs again; ⭐ but do re-read them, because the census pincomponent-docs-disabled-inherited-7239.test.tsnow claims that row and a further docs edit will move it.Boundaries:
ComboboxOptionto the existing named re-export list from./form.js.minorchangeset stating the addition. It is additive only — nothing removed, retyped or narrowed, and the@object-ui/types/formsubpath spelling keeps working.@object-ui/types/zod,@object-ui/types/internal/retired-field-keys). ⇒ this card is ⛔ not a defect in fix(components,plugin-grid,app-shell): one authority for ComboboxOption, NamedActionDef and OrgTranslate (objectui#6349 batch 3) #7691; it is the follow-up fix(components,plugin-grid,app-shell): one authority for ComboboxOption, NamedActionDef and OrgTranslate (objectui#6349 batch 3) #7691 could not take.Serial constraints cleared:
packages/types/**is free — all three of this wave'stypesPRs have landed (#8145registry.ts, #8154complex.*, and #8153's read-only use). ⛔ No other slot names it. The queue arbitrates.Generated by Claude Code
os-justin commentedon Sep 6, 2026
os-dev-report
{
"issue": 7697,
"status": "done",
"branch": "claude/issue-7697-combobox-option-root-barrel",
"pr": "#8177",
"premise_still_valid": true,
"summary": "Premise re-measured on the branch point a4611b3 and CONFIRMED: ComboboxOption read 0 hits on packages/types/src/index.ts while the three controls read non-zero (SelectOption 2, RadioOption 1, ComboboxSchema 1), and the ./form.js re-export is an explicit named list, not a wildcard. Both card line numbers had drifted as the PM said: the declaration is at form.ts:1409 and the named list ended at index.ts:184. Added ComboboxOption to that existing named list, next to ComboboxSchema, the way SelectSchema/SelectOption and RadioGroupSchema/RadioOption already sit. List NOT converted to a wildcard; declaration NOT moved. Added a three-layer pin (packages/types/src/tests/combobox-option-root-barrel-7697.test.ts) asserting BOTH spellings — type level per declared member through each entry point plus their invariant Eq equality, a source scan of the barrel clause that runs under pnpm test without reading dist/, and a manifest pin on exports['./form'] — each with its own control. Added a minor changeset. Blast radius measured across packages/, apps/ and examples/: exactly ONE declaration of the name in the repo and ZERO wildcard re-exports of @object-ui/types, so the addition cannot collide or shadow anything downstream. PR #8177 is DRAFT and carries needs:contract-review (Clause-2 yes, root-entry surface widening); label attached via the MCP fallback because the direct REST channel answered 403 for this seat, then read back comparatively — union of the read set (package: types, tests) with the target survived intact, nothing stripped. Assignee left untouched. content/docs/components/form/combobox.mdx was READ and deliberately NOT edited: it already carries the disabled? boolean row (2 hits) from PR #8142, the census pin component-docs-disabled-inherited-7239 claims it, and this diff does not touch the page, so that pin does not move and is green.",
"tests": "ALL RUN IN THE FOREGROUND VIA THE SHARED VERIFY LOCK (slot objectui-7697; first attempt returned exit 99 queue-timeout after 9m00s = NOT MEASURED, the place was kept, the resumed attempt acquired in 7s). GREEN: 'pnpm --filter @object-ui/types type-check' -> TYPECHECK_EXIT=0 across all three tsc legs (tsc --noEmit, tsconfig.examples.json, tsconfig.test.json). 'pnpm exec vitest run packages/types/ scripts/' -> VITEST_EXIT=0, 'Test Files 251 passed (251) / Tests 5965 passed (5965)' in 104.40s (shared-box seconds; scripts/ included on purpose because it holds the tree-level gates a root-barrel export addition is in reach of, one-authority-per-exported-name-6273 among them). eslint --format json on the 2 changed source files -> exit 0, 2 files linted, 0 errors 0 warnings. node scripts/check-changeset-presence.mjs -> its own verdict line: '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'. node scripts/check-control-bytes.mjs -> 'OK (scanned 6520 tracked text file(s))'; plus a manual grep -naP control-byte sweep of the three changed files, no hits. NOTE, not a red: 'pnpm --filter @object-ui/types^... build' exits 1 with 'None of the selected packages has a build script' — the dependency closure is genuinely empty (types has no workspace runtime dep, only external @objectstack/spec and zod); confirmed by that message rather than assumed. ABLATION, run on the COMMITTED tree under a trap ... EXIT INT TERM with absolute paths. Mutation leg: deleted the barrel line, proven ON DISK by exact-line hits 1 -> 0 plus the unified git diff HEAD hunk showing '- ComboboxOption,'. Result: tsc -p tsconfig.test.json -> TS2305 'Module "../index" has no exported member ComboboxOption' at line 76, plus four TS2322 from the Eq assertions that lost their type, and a measured ZERO errors naming '../form'; vitest on the pin file -> 1 failed | 9 passed, the single red being the source scan while the /form layer, the manifest layer and every control stayed green. That asymmetry (root spelling fails, subpath spelling does not) is the whole point of the pin. Restore leg: git checkout HEAD -- ABSOLUTE_PATH, proven BY STATE and not by an exit code — git hash-object gave 67b5bfc65fcce4736b9ed09f9aaf379ef27be631, byte-identical to the HEAD blob for that path, git diff HEAD empty, exact-line hits back to 1, and git status --porcelain empty at the end of the run. No dist/ is involved anywhere in this pin, deliberately, because this repo's per-PR test job runs with no build step ahead of it. DECLARED NARROWING: turbo ls --affected names essentially the whole monorepo (everything depends on @object-ui/types), so the full farm is left to CI. The narrowing rests on the two blast-radius measurements above plus the shape of the change (adding a name to an export list can only add a resolvable name). Repo-level eslint is likewise CI's; the targeted run is non-excluding because this repo configures no type-aware linting — no project: or projectService in eslint.config.js — so this diff cannot move the verdict on any file it did not touch.",
"mcp_calls": "9 — issue_read get, issue_read get_comments, create_pull_request, issue_read get_labels (failed: a PR is not an Issue in GraphQL), pull_request_read get, issue_write update (labels), pull_request_read get_status, search_pull_requests (label read-back), add_issue_comment (this report)",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
os-justin commentedon Sep 6, 2026
Landed as
f6375da3cvia PR #8177. Closing;pm:dispatchedstripped and the assignee cleared in the same stroke.Verified by CONTENT against a control proven before the merge — ⛔ never by sha:
ComboboxOptioninpackages/types/src/index.tsexport interface ComboboxOptioninpackages/types/src/form.tsThe second row is the leg that makes the first mean what it claims. A one-legged control could not distinguish "the name was re-exported from the barrel" from "the declaration was moved into the barrel" — and the latter is precisely the thing this card ruled out. The declaration staying put in
form.tsis what proves the additive reading.Lit control for the pre-merge zero:
SelectOption,RadioOptionandComboboxSchemaall read non-zero on the same file under the same query, so the zero was a reading rather than a dark instrument.CI at the ready-flip: 32 check runs enumerated against
total_count: 32— 28 success, 3 skipped, 1 failure, zeroin_progress, withLintread explicitly (green 22:35:47Z) and all four shards green. The single red wasLive E2E (informational), stood down on the structural argument rather than on marker-matching: its artifact upload reports exactly 1 file (the backend log), so noplaywright-report/and notest-results/were produced — Playwright never ran, and a diff that never executed cannot be implicated.⭐ Recorded because it is the best instrument work this seat has seen today: the dev caught its own regex defect before committing. The source scan's first pattern used a lazy body, matched two export clauses by running on to
index.ts'simport type { FormComponentSchema } from './form.js', and its own clause-count assertion caught it. That is the discipline — asserting on the shape of your instrument, not just its output.Generated by Claude Code