Repository navigation
feat(types)!: retire FilterUISchema.filters[].operator on the filter-ui node (objectui#10611) - #10760
Conversation
…ui node
filter-ui never read a per-filter operator: the renderer picks the control
from `type` and emits `{ values }` only, with no matching of its own. The key
is now a `?: never` tombstone on the TS face and a retirementTombstone() on
the zod mirror, refused by name at the entry's own path. The docs Schema
block drops it and states the retirement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
|
changeset-claim-re-read
|
✅ 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 reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: Blocking: (1) VERDICT: FAIL Generated by Claude Code |
… truthfully
The changeset and the pin header said an authored `operator: 'gt'`, "or a
nonsense id", type-checked and parsed green before the retirement. That is
false for the nonsense id: the old mirror was a seven-member z.enum and the
old TS member the same union, so a non-member was refused (invalid_value) and
did not type-check. Both now say that 'gt' type-checked and parsed green, a
nonsense id was refused by the enum, and both rendered and emitted exactly
what a filter without it does.
The emit sentence is made exact too: a host onChange function receives the
bare field -> value record; only the authored window event's detail is
{ values }. The AUTHORED docblock no longer credits the one-time render
reading with values it did not sample.
Prose and comments only; no code or test logic changes, and the changeset
front matter is byte-identical.
Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
Co-authored-by: Claude <noreply@anthropic.com>
…r-retired Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW Co-authored-by: Claude <noreply@anthropic.com>
✅ 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 ABSENT at the new head
Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgmentsScope of this record: the hop
② Semver levelUnchanged from 5853796261: ③ Boundary flags
Implemented-by: VERDICT: PASS Rendered at tier by the director seat (summon #30 续) on the fetched head. Not a governed path; the landing pre-checks are met on this head (fresh PASS, CI green, no conflict), so Generated by Claude Code |
Fixes #10611
Clause-②: yes
What changes
FilterUISchema.filters[].operatoris retired from thefilter-uinode on both published faces and on the docs page (ADR-0049 enforce-or-remove). The renderer never read it.packages/types/src/views.ts): the member is nowoperator?: never. Its docblock says why and carries a@deprecatedtag.packages/types/src/zod/views.zod.ts): the seven-memberz.enumis now aretirementTombstone(). One guidance string serves as both the parse-time issue message and the.describe()metadata. The refusal isinvalid_typeat the entry's own path (filters.N.operator). No replacement key is named, because nothing in this component implements operators.content/docs/components/complex/filter-ui.mdx): the Schema block no longer listsoperator. A new short section, "Retired:filters[].operator", states the retirement in prose. The page adds no new fence and leaves the JSON Configuration block untouched.minorfor@object-ui/types, with the breaking semantics stated. It is notmajor, because of the version-alignment rule.packages/types/src/__tests__/filter-ui-operator-retired-10611.test.ts.greater_thanand a nonsense id, is refused. The check runs atsafeValidateSchema(the doorobjectui validate/objectui checkrun) and on the mirror, and expects ONE issue{ code: 'invalid_type', path: ['filters', 1, 'operator'] }. The message equals the published describe string, and the key stays declared.operatorparse at both doors.Equalpins plus one@ts-expect-error, judged bytsc -p tsconfig.test.json.Measured before retiring (the card's stop condition)
The stop condition was a real producer that writes
operatoron afilter-uifilters[]entry, or a reader that honours it. None was found.H1, declaration. Confirmed on
origin/main1422a920ed, the branch point. The TS member is in theFilterUISchemafilterselement type. The mirror is thez.enum(['equals', 'contains', 'startsWith', 'gt', 'lt', 'between', 'in']).optional()in theFilterUISchemafilterselement.H2, no reader. Measured with a render probe, not a grep. The probe was a throwaway test file, deleted and not committed. It used a real
SchemaRendererand the real registry: a module-scopeimport '../index'of@object-ui/plugin-view. The node was onefilter-uiwith a number entry and a text entry, and ONLYfilters[0].operatorvaried: absent /gt/greater_than/startsWith/between/zz_nonsense_op. Each reading was taken from:7andab;onChangecalls;detail.Result: 6 variants gave 1 distinct reading. The payload was
{ values: { qty: 7 } }, then{ values: { qty: 7, name: 'ab' } }, and the host calls were the same records. There was no operator anywhere. Control:type: 'text'on the same entry gave a different reading.Source side, as a corroboration and not as the instrument:
FilterUI.tsxhas nooperatortoken and no...filter/...schemaspread. The entry is read only fortype,field,label,placeholderandoptions.git log -S operatorover the file returns 0 commits. The lit control,-S notifyViewHandlerChannelsover the same file, returns 1.328ad7a419), so this history reading covers that window only. The present-tree probe above is the load-bearing reading.Producer census. Every in-repo
filter-uiauthor was read, and none writesoperator:components-complex-filter-ui/filter-ui.json;defaultPropsand designerinputs;phase2-schemasfixture;plugin-viewtests;check-doc-expression-carriagecontrol fixture.Other places checked:
FilterUISchemaimporters: onlyplugin-view'sFilterUI.tsx, plus tests.RecordPickerDialogrenderFilterBardocblock bridge: it passesRecordPickerFilterColumn, whose members arefield/label/type/options, with nooperator.skills/**: 0filter-ui/FilterUIhits.origin/maine2c4e12:filter-uiin 0 files (control:object-gridin 53). The oneFilterUImention is prose in an ADR.H3, tombstone form. The
filters[]element is a plainz.object. It strips an undeclared key and is neither.passthrough()nor strict. Deleting the member would therefore have dropped an authored value in silence. Pin block (c) measures this on a misspelling.H4 / H5. The docs Schema block is rewritten. No designer input, skills text or example names the key, so nothing is split out.
Verification (at
e0fdfb5647, the first commit; the code and test logic are byte-identical at the final head, see Patch round 1)pnpm exec vitest run packages/types/src/__tests__/filter-ui-operator-retired-10611.test.tspnpm exec vitest run --maxWorkers=2 packages/types/pnpm exec vitest run --maxWorkers=2 packages/plugin-view/pnpm --filter @object-ui/types type-check(incl.tsc -p tsconfig.test.json)pnpm exec turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2pnpm --filter @object-ui/plugin-view type-check(against the rebuiltdist)Consumer direction.
@object-ui/typesis upstream, and its downstream consumer that typesFilterUISchemais@object-ui/plugin-viewalone. Both packages were run and both are green, as listed above.Ablation and reverse verification. Each leg ran through
ablation-replace.mjs(the anchor must hit, and the restore is proven by blob == HEAD and an emptygit diff HEAD).9b06cc60f1de.?: neverwas reverted to the old union, andtsc -p tsconfig.test.jsonexited 2. It reportedTS2344onassertionOperatorRetired,TS2578on the unused@ts-expect-error, and alsoTS2322fromzod-mirror-parity.test.tsonviews.zod.ts#FilterUISchema(the parity ratchet sees the pair drift). The file was restored to blob3a0237a738ed.const zzProbe10611: FilterConfig = { ..., operator: 'gt' }was planted inplugin-viewand run throughtsc --noEmit. It exited 2 withTS2322: Type 'string' is not assignable to type 'undefined', which provesplugin-viewreads the rebuilt.d.ts. The file was restored to blob651baa8e1956. The first attempt at this leg was refused by the tool (its replacement kept the anchor, so the mutation never landed). It is declared here and did not count.Gates (each exit code captured before any pipe). Every one of these exited 0:
node scripts/check-changeset-presence.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-changeset-overwrite.mjsnode scripts/check-changeset-fixed.mjspnpm check:new-line-citations(0 new citations)pnpm check:control-bytespnpm check:spec-symbolspnpm check:test-path-rootspnpm check:changeset-claimspnpm check:pending-changeset-literalspnpm type-check:coveragepnpm check:doc-typespnpm check:doc-fencespnpm check:doc-example-idspnpm docs:check-linksnode scripts/check-doc-expression-carriage.mjspnpm check:component-surface-paritypnpm check:designer-field-key-paritypnpm check:prompt-keyspnpm check:handler-key-readsnode scripts/check-governed-queue-guard.mjs --testover the five paths, which answered NOT GOVERNED.ESLint, as a proven narrowing. The run was
eslint --no-inline-config --format jsonover the three lintable touched files (views.ts,views.zod.ts, the pin). Three facts make this narrowing a measurement:eslint.config.js, whichpackages/types'eslint .resolves to.no-explicit-anyhits on untouchedviews.tsmembers, and this diff adds noany.parserOptions.project/projectService, so type-aware linting is off and this diff cannot move a verdict on an untouched file.Patch round 1 (final head
671407f3fb)The at-tier review
5853796261ofe0fdfb5647said FAIL on prose only. The changeset and the pin header said a nonsense operator id 「type-checked, parsed green」, which was false: the old seven-member enum refused it (invalid_value), and the TS union did not admit it. REWORK5853806677asked for exactly that fix.8eb899fd9drewrites the two texts.operator: 'gt', a member of the old enum, type-checked and parsed green; a nonsense id was refused and did not type-check; and both rendered and emitted what a filter without it does. The emit sentence is now exact: a hostonChangefunction receives the bare record, and only the authored window event'sdetailis{ values }. The pin'sAUTHOREDdocblock now says the render reading sampled a subset. Every changed pin line is a comment, and the changeset front matter is byte-identical.671407f3fbmergesorigin/main0896838deb, with no conflict and no hand edit.671407f3fb, each exit 0:packages/types/, 246 files and 5343 tests;pnpm --filter @object-ui/types type-check;check-changeset-presence,check-changeset-no-major,check:changeset-claims,check:new-line-citationsandcheck:control-bytes.5856250843of671407f3fbsays PASS. CI at that head: 40 success, 3 skipped.Acceptance notes
pnpm check:doc-snippets,pnpm check:doc-examples(exit 2, precondition not met). They need the 34-package build their--build-filternames. The fence this PR edits isplaintext, and it is the one blockcheck:doc-fenceslists for this file in its declared debt (unchanged at 1), so it is outside the snippet gate's judged population. CI runs both.FilterUISchemais not spec-derived:@objectstack/specdoes not namefilter-ui, and the diff adds or moves no spec import.check:spec-symbolsis green.check:sdui-registration-pinsandcheck:readme-exports. They need a console build or a whole-tree build, and the diff touches no registration and no README.typeunion omitsmulti-select, which both faces declare andFilterUIrenders. It under-teaches but invites no wrong metadata. It is pre-existing and outside this card. The director seat's review record5856250843attaches a carrier card for it.Session:
https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYWGenerated by Claude Code