Skip to content

fix(components,plugin-grid,app-shell): one authority for ComboboxOption, NamedActionDef and OrgTranslate (objectui#6349 batch 3) - #7691

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6349-name-authority-batch-3
Sep 5, 2026
Merged

fix(components,plugin-grid,app-shell): one authority for ComboboxOption, NamedActionDef and OrgTranslate (objectui#6349 batch 3)#7691
os-sam merged 1 commit into
mainfrom
claude/issue-6349-name-authority-batch-3

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Refs #6349 — batch 3 of the one-authority ledger burn-down. Not a closing reference: 30 ledger rows remain after this PR and the card stays open as their owner.

Clause-②: yes — my own determination, not only the dispatch's. Re-pointing ComboboxOption moves what the published @object-ui/components root entry declares under that name (it gains the optional disabled member), and a probe shows a literal the base declaration REFUSED is ACCEPTED at head. Draft, needs:contract-review, not ready, not enqueued, no auto-merge. Implemented by the os-dev seat in session session_01KbJQ1y1J12nZxYzFWhP8Q3.

Ledger reproduced on my base — 33, not 36

Merge-base a472b0716. KNOWN_COLLISIONS printed and counted by row: 33 entries. The sizing comment on the card read 36 at ac8abb0f5; the three-row delta is KanbanCard / KanbanColumn / KanbanSchema, removed by PR #7643 (2c71482ea, 2026-09-04, after that reading), and the ledger's own inline comment already records them as converged. All five batch-3 names were still present on my base; of the exclusions, CalendarEvent and FilterBuilderOperator are present and the Kanban trio is already gone. After this PR: 30.

The five entries — what was decided, and why

name verdict one authority the other name shape evidence
ComboboxOption re-point @object-ui/types (packages/types/src/form.ts) packages/components/src/custom/combobox.tsx becomes export type { ComboboxOption } from "@object-ui/types/form" components' copy was a strict SUBSET: keyof = value, label vs value, label, disabled; every shared member typed identically; assignable both ways; not identical (tsc probe against the built d.ts, exit 0; lit control asserting identity reds TS2344)
NamedActionDef re-point packages/plugin-grid/src/resolveLegacyRowActions.ts resolveBulkActions.ts imports and re-exports it textually and structurally IDENTICAL (Equal probe exit 0); not on the package's public entry
OrgTranslate re-point packages/app-shell/src/console/organizations/orgErrorMessage.ts orgRoleLabel.ts imports and re-exports it IDENTICAL (Equal probe exit 0); not on the package's public entry
FilterBuilderCondition stop-and-report row stays, annotated in the ledger see below
FilterGroup stop-and-report row stays, annotated see below

Why re-point rather than rename in all three: the card's rule is rename only where the shapes are genuinely different things. Two are identical and one is a subset whose header on both sides describes the same concept (the option a ComboboxSchema carries). The losing files keep exporting the name as a plain re-export, so an import from the losing module resolves to the SAME shape it always did — no consumer silently gets a different type. The ./form subpath is the door for ComboboxOption because the @object-ui/types root barrel does not publish that name (only ComboboxSchema, SelectOption, RadioOption); adding it to the barrel would have touched a file another dispatch holds.

The Filter pair is inseparable from objectui#7561 — measured, not judged

The dispatch warned that FilterBuilderCondition carries an operator typed by the excluded FilterBuilderOperator, and that if the NAME could not be re-pointed without touching the operator VOCABULARY it was a stop-and-report. It cannot:

  • Shapes. Component: { id: string; field: string; operator: string; value: string | number | boolean | array } and FilterGroup { id: string; logic; conditions: FilterBuilderCondition[] }. Types: { field; operator: FilterBuilderOperator; value?: any } (no id) and FilterGroup { id?: string; logic; conditions: (FilterBuilderCondition | FilterGroup)[] }. Neither side is a subset of the other, yet BOTH headers claim one concept (the component's: "this is the same thing, so it takes the same name"; the types': "the shape FilterBuilder reads"). One concept with drift is the re-point class, not the rename class.
  • Direction. @object-ui/types has zero dependencies, so the only dependency-legal re-point is components → types.
  • Measurement, prediction written first. Control: tsc --noEmit -p packages/components on the committed tree — exit 0, 0 errors. Mutation: replace the component's two declarations with a re-export of the types pair (mutation proven on disk: 2 declarations → 0, re-export 0 → 1, blob d27229586cfb2233e8), same tsc — exit 2, 29 errors: 2× Type 'string' is not assignable to type 'FilterBuilderOperator' (the dropdown's camelCase ids meeting the snake_case union), 25 errors on id / value / operator not existing on the recursive union, 1× TS2353 'id' does not exist in type 'FilterBuilderCondition'. Restored with git checkout HEAD -- on the absolute path under an EXIT INT TERM trap; git diff HEAD empty, blob back to d27229586. Direction and both predicted error classes matched.

So converging the name retypes operator, which is exactly the authority #7561 asks a maintainer to decide (its options 1 and 2 are "mirror follows dropdown" and "dropdown follows mirror"). The vocabulary-neutral alternative — declaring them two concepts and RENAMING one side — contradicts both headers, breaks a published name (plugin-list and plugin-view import FilterGroup from @object-ui/components), and would likely be undone by #7561's ruling. Both rows stay in KNOWN_COLLISIONS with a comment saying why; the decision goes back to the PM in the report.

Published surface — measured on the emitted declarations

Built components, plugin-grid and app-shell at base (a472b0716, snapshot of every .d.ts) and at head (76b737e41), full recursive diff:

  • @object-ui/components: 208 vs 208 files; exactly ONE differs, dist/custom/combobox.d.ts — the interface becomes export type { ComboboxOption } from '@object-ui/types/form'. dist/index.d.ts byte-identical.
  • @object-ui/plugin-grid: 31 vs 31; only dist/resolveBulkActions.d.ts (interface → re-export of ./resolveLegacyRowActions.js). dist/index.d.ts byte-identical; the name was never on the entry.
  • @object-ui/app-shell: 453 vs 453; only dist/console/organizations/orgRoleLabel.d.ts (alias → re-export of ./orgErrorMessage.js). dist/index.d.ts byte-identical; not on the entry.

Probes against the built base vs head dist/index.d.ts of @object-ui/components, predictions written first:

  • acceptance: a literal with disabled: true — base exit 2 TS2353 ('disabled' does not exist in type 'ComboboxOption'), head exit 0. This is the clause-② content limb firing.
  • keyof: a Record keyed by keyof ComboboxOption with the two old keys — base exit 0, head exit 2. ⚠️ Predicted TS2739, observed TS2741 (Property 'disabled' is missing — the single-property spelling of the same diagnostic). Direction matched; the code did not; no assertion rested on the code. So: every VALUE that type-checked before still does (nothing narrows, no key changes type); a consumer that EXHAUSTS the type breaks. In-repo keyof ComboboxOption consumers: zero.
  • negative control both sides: value: 1TS2322 on base AND head, same message.

Note for the reviewer: the Combobox component never reads option.disabled, so the member now visible on the components face is the same phantom already declared on the types face. Behaviour unchanged here; recorded as objectui#7687 (out of scope: a behaviour defect, not a naming one).

Grading: @object-ui/components minor (a published declaration gaining a member — the #6574 / #6936 precedent), plugin-grid and app-shell patch.

The gate is non-vacuous in both directions — five legs on the committed tree

Each leg: mutate the FACT not the assertion; prove the mutation on disk by anchored count AND git hash-object movement off the HEAD blob; run the gate; restore with git checkout HEAD -- on an absolute path from git rev-parse --show-toplevel under trap … EXIT INT TERM; prove the restore by empty git diff HEAD AND blob equality. Prediction written before each run.

leg mutation predicted observed
A fresh delete the still-live DomProps row exit 1, DomProps — a NEW colliding name, 1 failed of 11 exactly that (blob 49574c8ac5c43b44ed → restored)
B fresh (source) re-declare OrgTranslate in orgRoleLabel.ts in place of the re-export OrgTranslate — a NEW colliding name exactly that (blob 679e9b8e2ee4b0d8c8 → restored)
C1 stale re-add the ComboboxOption row no longer collides at: listing BOTH files (a single-file name has no observed entry) exactly that
C2 stale re-add the NamedActionDef row same shape, both plugin-grid files exactly that
C3 stale re-add the OrgTranslate row same shape, both app-shell files exactly that

Legs C1–C3 are the direct proof that each convergence is real rather than a ledger edit. Final tree state after the battery: 0 modified paths.

Verification on the final commit 76b737e41

Exit codes captured by redirect before any pipe; verdict lines are the tools' own.

  • pnpm exec vitest run --project unit scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 1 passed (1), Tests 11 passed (11).
  • Root-form vitest, identical file set before (BASE worktree) and after: scripts/__tests__/one-authority…, packages/components/src/custom/, packages/components/src/renderers/form/, the two plugin-grid resolve* tests, packages/app-shell/src/console/organizations/ — BEFORE Test Files 72 passed (72) / Tests 529 passed (529); AFTER 72 / 529.
  • pnpm --filter @object-ui/components type-check, …/plugin-grid type-check, …/app-shell type-check (hyphenated; script echoed, tsc --noEmit && tsc -p tsconfig.test.json) — exit 0 each; --listFiles shows each edited source file in its program (1 hit each).
  • pnpm run type-check:scripts (covers the edited ledger file) — exit 0.
  • pnpm exec eslint . plain, whole repo — exit 0, 11928 problems (0 errors, 11928 warnings).
  • Full workspace build turbo run build — 43/43 successful; then check:readme-exports✅ check-readme-exports: OK (… 421 self-imports judged (421 real, 0 wrong-path, 0 fabricated) …).
  • check:control-bytes (6258 files OK) · check:esm-specifiers · check:doc-types · check:spec-symbols · check:self-import · check:phantom-deps · check-changeset-presence (3 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)) · check-changeset-no-major — all exit 0.
  • check-governed-queue-guard --test on all five paths: NOT GOVERNED.

Fences

git diff --name-only a472b0716..76b737e41: .changeset/6349-name-authority-batch-3.md, packages/app-shell/src/console/organizations/orgRoleLabel.ts, packages/components/src/custom/combobox.tsx, packages/plugin-grid/src/resolveBulkActions.ts, scripts/__tests__/one-authority-per-exported-name-6273.test.ts. No file under packages/types/src/ (so no complex.ts, no registry.ts, no mobile.ts, neither barrel), nothing of #7546 / #7519 / #7662 / #7669. Only one #6349 branch exists on the remote.

Placeholders in this body are spelled as words rather than angle-bracket shapes because GitHub's sanitizer strips tag-shaped fragments from stored bodies.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3


Generated by Claude Code

…on, NamedActionDef and OrgTranslate

Batch 3 of the one-authority ledger burn-down. Three names come off
KNOWN_COLLISIONS (33 -> 30), each by re-pointing the losing file at the
one authority with a plain `export type { X } from` re-export, which the
gate does not count and which keeps every existing import resolving to
the same name:

- ComboboxOption: the component's `{ value, label }` was a strict subset
  of the @object-ui/types declaration (`{ value, label, disabled? }`),
  so packages/components/src/custom/combobox.tsx now re-exports it via
  the `./form` subpath (the root barrel does not publish the name).
- NamedActionDef: identical in both plugin-grid folds;
  resolveLegacyRowActions.ts is the authority, resolveBulkActions.ts
  re-exports.
- OrgTranslate: identical in both organization helpers;
  orgErrorMessage.ts is the authority, orgRoleLabel.ts re-exports.

FilterBuilderCondition / FilterGroup stay in the ledger, annotated: the
shapes disagree on id, value and operator, and the only dependency-legal
re-point retypes `operator`, which is the vocabulary objectui#7561 asks
a maintainer to rule on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Contract review — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 132/132 assistant turns stamped claude-fable-5-1, 0 fallback notices, against a lit control that reads claude-opus-5 in this session's other transcripts. Adopted verbatim — not edited, trimmed or paraphrased.


Contract review — PR #7691 (claude/issue-6349-name-authority-batch-3, head 76b737e41, merge-base a472b0716) against issue #6349 batch 3

Instrument. Two detached scratch worktrees at a472b0716 and 76b737e41, pnpm install --frozen-lockfile in each (exit 0 / 0). The first turbo run build pass on both trees was a 100 % cache restore (29 cached, 29 total, 412 ms / 379 ms), which is not a fresh emit; my first bypass attempt then tripped the repo's own objectui#6703 trap (turbo also restores tsconfig.tsbuildinfo, so tsc emitted nothing and check-dist-completeness refused with missing 120 of 120 files). All .d.ts readings below come from a second pass that deleted dist/ and *.tsbuildinfo for types, components, plugin-grid, app-shell and ran each package's own build script: base 60 / 208 / 31 / 453 .d.ts, head 60 / 208 / 31 / 453, every build exit 0, both trees git status --porcelain = 0 before and after. TypeScript 6.0.3. Probes ran from a separate consumer directory whose node_modules/@object-ui/* symlink to each tree's packages, so every import goes through the real exports map. No GitHub writes of any kind; the shared checkouts were read only (/home/user/objectui HEAD 77b3cf1f5, 0 status lines, unchanged). Scratch worktrees and probe files removed at the end.


① Derived judgments

Each line: the change, RIGHT/WRONG, and the measurement that decides it.

  1. ComboboxOption — re-point packages/components/src/custom/combobox.tsx at @object-ui/types via @object-ui/types/form: RIGHT. Fresh emit diff for @object-ui/components: 208 vs 208 .d.ts; exactly one differs, dist/custom/combobox.d.ts (the interface becomes import { ComboboxOption } from '@object-ui/types/form' + export type { ComboboxOption } from '@object-ui/types/form'); dist/index.d.ts byte-identical (sha1 23fdc71c82bc both sides); 2 dist entries differ in total (the .d.ts and its map). The byte-identical root is not evidence the surface stood still: the name reaches the root through dist/index.d.ts:10 export * from './custom/index.js'dist/custom/index.d.ts:3 export * from './combobox.js', so the surface moves one hop below a barrel that cannot change. Verified on the built root through the exports map (moduleResolution: bundler), predictions written before each run and all met: acceptance { value, label, disabled: true } base exit 2 TS2353 / head exit 0; Record<keyof ComboboxOption, 1> with two keys base exit 0 / head exit 2 TS2741; negative control value: 1 TS2322 on both; the old literal { value, label } exit 0 on both; Base extends Head and Head extends Base both hold on both trees; identity Eq<{value;label}, ComboboxOption> base exit 0 / head TS2344, with a lit control (Eq<ComboboxOption, {value:number}>) reading TS2344 on both trees. Same-symbol probe: base Eq<components.ComboboxOption, types/form.ComboboxOption> false (two declarations), head true, and head also equals NonNullable<ComboboxSchema['options']>[number] — the component's option is now literally the option the published ComboboxSchema carries. Clause-② yes: RIGHT, and the author's numbers reproduce exactly.

  2. ComboboxOption blast radius (the widening): RIGHT as stated in the changeset. Cross-tree probe importing both trees' dist/custom/combobox.d.ts in one program: base shape assignable to head shape and vice versa (A1, A2 silent), keyof base = 'value'|'label', keyof head = 'value'|'label'|'disabled' (A3, A4 silent), identity false (A5 the only TS2344). So every value that type-checked before still does, nothing narrows, no key changes type; only an exhaustive-keyof consumer breaks. Exhaustive consumers in the repo: git grep 'keyof ComboboxOption' and Record<…ComboboxOption = 0 outside the changeset text; importers of the losing module are two tests importing Combobox only plus custom/index.ts. Nothing in-repo breaks or silently changes meaning.

  3. NamedActionDefresolveLegacyRowActions.ts the authority, resolveBulkActions.ts re-exports: RIGHT, and patch for @object-ui/plugin-grid is RIGHT. 31 vs 31 .d.ts; exactly dist/resolveBulkActions.d.ts (+ its .d.ts.map) differs; dist/index.d.ts byte-identical (c7f45e9921c5); dist/index.d.ts mentions neither module (0 hits) and the package exports map is '.' + './style.css' only, so the deep module is not reachable by any exports-aware consumer. Cross-tree identity on the deep dist paths: base-losing ≡ head-losing, base-authority ≡ head-authority, head-losing ≡ head-authority, base-losing ≡ base-authority — all four true; lit control Eq<NamedActionDef, {name:number}> TS2344.

  4. OrgTranslateorgErrorMessage.ts the authority, orgRoleLabel.ts re-exports: RIGHT, and patch for @object-ui/app-shell is RIGHT. 453 vs 453; exactly dist/console/organizations/orgRoleLabel.d.ts differs (alias → import type/export type … from './orgErrorMessage.js'); root byte-identical (b169fb524967); 0 mentions on the entry; exports is '.' + './styles.css'. Cross-tree: all four identities true; lit control Eq<OrgTranslate, (k:number)=>string> TS2344.

  5. @object-ui/types emitted surface untouched: RIGHT (control). 60 vs 60, 0 differing dist entries, root sha1 925771f6e32c both sides — consistent with git diff --name-only a472b0716..76b737e41 containing no packages/types/src/ path.

  6. The @object-ui/types/form door: RIGHT on its own merits, not a workaround that strands the name. --traceResolution from the head combobox.d.ts: bundlerwas successfully resolved to …/types/dist/form.d.ts; node16 (ESM consumer) → same, and the head keyof probe reads TS2741 under node16 (proof the type is not any); node10was not resolved, and inside a skipLibCheck consumer that is silent (ComboboxOption becomes any; head node10 accept exit 0 and keyof exit 0 for the wrong reason; a direct import … from '@object-ui/types/form' reads TS2307 at both trees). Whether that is a new exposure class is what decides the judgment, and it is not: with skipLibCheck: false the base components .d.ts already yields 112 TS2307 under node10 (41 @objectstack/spec/ui, 19 @objectstack/spec/data, 16 class-variance-authority/types, 11 spec/api, 8 spec/contracts, …; @objectstack/spec 17.2.0 ships no typesVersions either), head 114 (exactly the two new types/form lines); under bundler 0 / 0, with the 10 residual diagnostics identical on both trees (all TS2591 Buffer/http inside third-party node_modules declarations). TS 6.0.3 itself rejects moduleResolution=node10 without ignoreDeprecations ("will stop functioning in TypeScript 7.0"), and the repo's consumer guidance (skills/objectui/guides/project-setup.md:147) prescribes bundler. The subpath is also a house pattern already in published source (@object-ui/types/zod in plugin-map, cli; @object-ui/types/internal/retired-field-keys in app-shell, plugin-designer). Reachability: the name is reachable exactly where it was (the components root) and remains, as before, absent from the types root (import { ComboboxOption } from '@object-ui/types' reads TS2305 on both trees — a pre-existing gap this PR neither creates nor closes; the root publishes ComboboxSchema, SelectOption, RadioOption only). Fence: PR feat(types,mobile)!: retire the MobileResponsiveConfig and GestureConfig published types #7683 (45df4a2ec) does edit packages/types/src/index.ts, so a barrel line would have been both a fence breach and a second published-surface addition on another package.

  7. Ledger edits: RIGHT. Rows counted by [' lines inside KNOWN_COLLISIONS: base 33, head 30; the three removed rows are exactly ComboboxOption, NamedActionDef, OrgTranslate; FilterBuilderCondition / FilterBuilderOperator / FilterGroup and CalendarEvent remain. Current origin/main (d3f5256ce) still reads 33; git merge-tree --write-tree d3f5256ce 76b737e41 exit 0 (clean); the 13 files main moved since the merge-base overlap none of the PR's 5.

  8. Gate non-vacuity: RIGHT — the gate is lit in both directions. Clean controls: 11/11 on both trees. On the committed head tree, each leg mutated the fact, was proven on disk by anchored count and blob movement, run, restored with git checkout HEAD -- under a trap, and proven restored (git diff HEAD empty, blob equal, porcelain 0):

    leg mutation observed
    A fresh delete live DomProps row (blob 49574c8ac5c43b44ed) exit 1, DomProps — a NEW colliding name, 1 failed / 10 passed
    B fresh via source re-declare OrgTranslate in orgRoleLabel.ts (679e9b8e254865a348) exit 1, OrgTranslate — a NEW colliding name
    C1 / C2 / C3 stale re-add each converged row exit 1, <name> — no longer collides at: listing both files each time
    D (mine) base's whole ledger (906488fbe) under head's sources exit 1, three no longer collides blocks naming all six files

    C1–C3 and D are the direct proof the three convergences are real source changes, not ledger edits.

  9. Disclosed prediction miss (TS2739 predicted, TS2741 observed): RIGHT that nothing rested on it. Synthetic lit control on TS 6.0.3: Record<keyof X,1> missing one key → TS2741, missing two → TS2739. The author predicted the plural spelling for a single new key; direction, exit code and the assertion ("a consumer that exhausts the type breaks") were unaffected.

  10. Verification union at head (my re-run): tsc --noEmit exit 0 for components, plugin-grid, app-shell against the fresh dist; type-check:scripts exit 0 (covers the edited gate file); eslint on the four changed source files exit 0; check-changeset-no-major ✅, check-changeset-presence ✅ (3 source file(s) of 3 released package(s) changed … 1 changeset), check-changeset-overwrite ✅; fence held (git diff --name-only = the 5 declared files); git ls-remote 'refs/heads/claude/issue-6349*' = 1 branch (the serial-batch constraint held).

② Semver grading

Declared: @object-ui/components: minor, @object-ui/plugin-grid: patch, @object-ui/app-shell: patch. Correct level: the same. AGENTS.md §版本号策略 (lines 236–241) pins objectui's major to @objectstack's and requires objectui's own breaking changes to be declared minor with the breaking semantics in the body; major is refused by scripts/check-changeset-no-major.mjs (passes here). The components change is a published declaration gaining an optional member — value-compatible in both directions, breaking only for exhaustive-keyof consumers — so it is minor on either reading, and the body spells out precisely that caveat plus the unread-disabled note. plugin-grid and app-shell change no reachable surface (measured in ①.3/①.4), so patch is right. The 40-package fixed group takes the highest bump, minor, exactly as batches 1 (#6936) and 2 (#7566) and the Kanban trio (#7643) landed ('@object-ui/types': minor); the cited #6574 precedent is likewise types: minor / plugin-list: minor / plugin-dashboard: patch. Every sentence of the changeset body I could measure is accurate.

③ Boundary-flag disposition

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants