Uh oh!
There was an error while loading. Please reload this page.
refactor(types): converge the named select-option types on one spec-derived base - #7538
Conversation
…ec-derived base
`SelectOptionMetadata` (the object-metadata read model) and `SelectOption` (the
SDUI form vocabulary) each restated the select-option vocabulary by hand. Both
now extend the new `SelectOptionBase`, which derives the spec's keys from
`@objectstack/spec/data` by reference and writes out only the divergences.
The convergence is an EXTENSION, not a replacement: objectui legitimately
carries `description` (LookupField searches it), `disabled` and `icon`, none of
which the spec declares — its `SelectOptionSchema` is strict over exactly
`{label, value, color, default, visibleWhen}` and refuses all three by name.
Each is declared as objectui dialect with that refusal written into the
published JSDoc rather than described as spec-aligned.
Nothing narrowed. `SelectOption` resolves member-for-member to what it resolved
to before; `SelectOptionMetadata` gains exactly one optional spec key,
`default`, which that face could not describe before. Both are pinned
invariantly against the pre-convergence member lists.
Ref objectui#7014
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC✅ 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
|
Addendum — the Tier-1 row-3 question, answered, and a merge-order note. Posted as a comment rather than edited into the body: this repo has measured that a body 1. Merge order and conflicts. The dispatching seat set the order: the 2. Tier-1 row 3 stays out of this PR, and now has a card: objectui#7540. The question raised was whether widening the module-local The keys are lost in Doing only the type half would have been worse than doing nothing: it would declare two keys the editor still cannot carry, which is the declared-but-not-carried shape this very card exists to remove. Re-measured on the installed spec, both controls lit before any row was trusted (a one-character Both dropped keys are keys the spec accepts, and Generated by Claude Code |
os-project-manager
commented
Sep 3, 2026
合约审查裁断:PASS —— 逐字采信
转录档位核验:只提取 harness 生成的
VERDICT: PASSReviewed: Card state (all 8 comments read): ruled — Q1 = option A (Tier-1 named types only, narrow; convergence is an EXTENSION; dialect keys declared as repo dialect, never "aligned with the spec"); Q2 = the What I measured, and what each measurement rules out1. "Nothing narrowed" — my own instrument, not the PR's pin. 2. Derivation by reference, and the 3. JSDoc claims, verified with my own controls ( Every by-name refusal in the 4. Census. 5. The pin is non-vacuous and inside the checked program.
6. Gate readings, re-derived on all three states ( 7. Narrowing 7 judged and closed. 8. The exclusion of Tier-1 row 3. Read in place: Also verified: 7 pins → NARROWINGS
派发席处置放行。 ⭐ 值得指出:本席原本准备用一条推理闭合它最重的 narrowing(「合并态全仓 type-check 由合并队列自身闭合——队列会把 PR 与 main 合出来跑完整 CI」)。审查者在收口轮里直接把它测掉了(
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #7014 — Q1 only (the Tier-1 named-type convergence). The card's other executable half is being implemented in parallel on a different file by another seat, and the gate-widening questions are ruled elsewhere; neither is described or touched here.
What this does
SelectOptionMetadata(packages/types/src/field-types.ts, the object-metadata read model) andSelectOption(packages/types/src/form.ts, the SDUI form vocabulary) each restated the select-option vocabulary by hand. Both now extend one new declaration,SelectOptionBase(packages/types/src/select-option.ts), which derives the spec's keys from@objectstack/spec/databy reference:The three declarations, spelled out in words rather than in angle-bracket generic syntax — this repo has measured GitHub silently eating tag-shaped fragments out of a stored body, inside fenced code blocks and backticks too, and a before/after table of
.d.tstypes is exactly the shape it collapsed:SelectOptionBase— the spec's ownSelectOption, with the single keyvisibleWhenremoved via OMIT, then re-declared on objectui's wire shape, plus the two objectui-only keysdisabledandicon.SelectOptionMetadata— EXTENDSSelectOptionBase, adding the one keydescriptionand nothing else.SelectOption— EXTENDSSelectOptionBasewith the single keyvalueremoved via OMIT, then re-declared as the union of string, number and boolean.A key the spec adds now reaches both faces with no edit here; a key it removes becomes a compile error at the sites that read it, instead of a hand copy that goes on compiling while the contract moves underneath it. It is the same form
metadata-viewer.tsxandform-spec.tsalready use — each divergence named in anOmitrather than dropped by silence.The convergence is an EXTENSION, not a replacement
objectui legitimately carries keys the spec does not:
description(LookupFieldsearches it on authored static options, objectui#6153) on the metadata face, anddisabled/iconon both. Re-measured on the installed spec for this PR, every refusal paired with a control that accepts the same payload minus the key:The negative control matters and is why the ACCEPT line is a reading rather than luck: a select option's
valueis a machine identifier with a minimum length, so a one-character value failstoo_small— a different reason — and a control that red for that reason would have measured nothing.So each objectui key is declared as repo dialect with that by-name refusal written into the JSDoc. It is deliberately not described as "aligned with the spec" — that is the exact class of false claim PR #7510 corrected, and this PR does not reintroduce it. The JSDoc wording matters beyond this repo:
//line comments are stripped by TypeScript's declaration emit but/** */blocks are carried into.d.tsverbatim, so these notes are published. They are written to stand alone for someone reading only the.d.ts— no pointers at comments that do not travel — and they carry no version number that can rot.Nothing narrowed. One key widened, and it is named
SelectOptionis member-for-member exactly what it was before.SelectOptionMetadatais exactly what it was before plusdefault?: boolean— a spec key that face could not describe, ruledenforceon the object-field face (objectstack#7246), arriving OPTIONAL so every document that face accepted before is still accepted.Both are pinned invariantly (
Equal, notextends, since a one-way check passes on a narrowing) against the pre-convergence member lists written out by hand inselect-option-tier1-convergence-7014.test.ts, and the addition is pinned as a set difference so a second key cannot ride along silently.Census of every site that names either type today, and why none is narrowed —
packages/types(SelectFieldMetadata.options,LookupFieldMetadata.options,views.ts,SelectSchema.options,FormField.options),@object-ui/fields(index.tsxreadsSelectOptionMetadata),@object-ui/components(renderers/form/form.tsxreadsSelectOption), plus the pins inbulk-action-param-options,field-metadata-rows-option-description-6140,zod-mirror-parity,disabled-twin-symmetry-7087andselect-option-spec-parity. TheSelectOptionnamed inapp-shell/form-spec.tsand inexamples/schema-catalogis the spec's symbol, not this package's, and is untouched. All three packages type-check and all their tests pass, below.The third Tier-1 site named by the card — the module-local
interface OptioninObjectFieldInspector.tsx— is deliberately not in this PR; see the note at the end.Verification
All readings at
3c7aa670a(the final commit; the gate union below was re-run on it).packages/types/src/__tests__/select-option-tier1-convergence-7014.test.ts, with the five neighbour pins that touch these shapes:Test Files 7 passed (7)/Tests 157 passed (157).turbo run type-checkfor@object-ui/types,@object-ui/fields,@object-ui/components:Tasks: 13 successful, 13 total, each echoing its own script name so a zero-match silent pass cannot read as green.packages/fields/andpackages/components/src/renderers/form/:Test Files 189 passed (189)/Tests 2536 passed (2536).tsc -p packages/types/tsconfig.test.json --listFilesreports 556 files, 1 hit each for the new pin, the existingselect-option-spec-extension-7014pin andsrc/select-option.ts, and 0 for a control filename that does not exist.check:spec-symbols—18 declared dialects, 14 untriaged collisions in 7 packagesand2 declared deliberate copies, 19 unbacked claims in 5 packages, both unchanged from the pre-change baseline onorigin/main(files scanned 1343 to 1344, the new module);check:control-bytes—OK (scanned 6204 tracked text file(s); skipped 85 binary);check:dist-completeness—7 package(s) complete (524 emitted files verified);check:changeset-presence—5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s);check:changeset-no-major—No changeset declares a major bump; pluscheck:esm-specifiers,check:self-import,check:phantom-deps,check:published-tsconfig-exclude,check:type-check-coverage,check:lint-coverage, the changeset fixed-group guard and the changeset-overwrite guard, all exit 0.pnpm lintwas not run;packages/typeswas linted in full instead — broader than the changed files —268 problems (0 errors, 268 warnings), all pre-existingno-explicit-any. Safe because ESLint here is not type-aware (noprojectService, noparserOptions.projectineslint.config.js), so this diff cannot move an untouched file's verdict. ESLint's own count over the five changed files:5 linted, 0 errors, 16 warnings, every warning a pre-existingno-explicit-anyfar from the edits, and 0 warnings on either new file.pnpm lint, and any gate needing a full 40-package build (check:readme-exportsand the load leg ofcheck:node-esm-load). CI runs those.Reverse verification — predicted red set written before the first run
Implementation committed first, then mutated; no build is involved on either leg (
packages/typestests import the sibling module by relative path andtscreadssrc/).M1, an implementation mutation — drop
icon?: stringfromSelectOptionBase, which is the classic way a "unification" narrows. Mutation proven on disk in both directions before any reading: injected marker present once, removed text absent, blobb6f7d14ftof852eb10. Predicted 6 red sites in the new pin and a green vitest run (types are erased at runtime, so this mutation is a compiler finding by construction). Measured: 8tscerrors — the 6 predicted, plus one more site in the same file I had not enumerated, plus one outside the new pin,bulk-action-param-options.test.ts:143, an existing neighbour that readsiconoffSelectOptionMetadata. So the narrowing is caught in two independent places, not one. The two predicted-green assertions stayed green, and vitest stayed green exactly as predicted.M2, a pin mutation — drop
'color'from the runtime spec-key list, to show the runtime half is measuring rather than vacuously green. Blob844e8838tod1ffc324. Predicted exactly one red test and a cleantsc. Measured exactly that:Tests 1 failed | 7 passed (8), the failure beinglists exactly the keys the spec declares, with all three by-name refusals and their controls still green, andtscat 0 errors.Restoration proved BY STATE, not by exit code, on both legs and under a
trap … EXIT INT TERMwith absolute paths: worktree blob equal to the HEAD blob for each file, injected marker count back to 0,git diff HEADempty andgit status --shortempty.Deliberately not in this PR
interface Optioninpackages/app-shell/.../ObjectFieldInspector.tsx. The dispatch fences that file and its localOptiontype is separately ruled untouched, so converging it is not this PR's to make. Flagged in the report rather than done quietly, because the card counts Tier-1 as three named types and this PR converges two.content/docs/fields/lookup.mdx:68still tells readers the optiondescriptionkey is aligned with the spec'sSelectOptionSchema.description, the same false claim PR fix(types,fields): correct three false spec-alignment claims and pin the real boundary #7510 corrected in the JSDoc, and an unconsumed changeset carries the sentence toward the release notes. Filed separately because the fix is a docs-only prose change that pulls in the docs gate family this diff does not otherwise touch.Draft, and labelled
needs:contract-review: this moves published types, so it waits for the dispatching seat's contract review rather than being made ready here.Generated by Claude Code
Generated by Claude Code