Repository navigation
feat(filter-builder): the dropdown speaks the protocol's operator ids; camelCase is read as a deprecated alias (objectui#9306) - #10534
Conversation
…— checkpoint, stopped on a file-surface breach CHECKPOINT, NOT A DELIVERABLE. Source half of the dropdown change only; the pins that read these tables are NOT updated yet, so 19 test files are red on this commit by design. Committed so the work survives the stop and a takeover starts from the remote sha rather than from nothing. - components: `defaultOperators` emits the 20 `VIEW_FILTER_OPERATORS` ids plus the opt-in `exists` / `notExists`; a read boundary folds stored spellings through the spec's `normalizeFilterOperator` plus ONE local row (`containsCaseInsensitive` -> `icontains`, which the spec's alias table lacks); buckets typed as the id union; `icontains` leaves OPT_IN_OPERATORS (the list parity spine measures it expressible on both list dialects). - fields: `condToMongo` / `kvToCondition` / `TEXT_COMPARAND_OPERATORS` / `FILTER_CONDITION_EXTRA_OPERATORS` keyed on the protocol ids; stored `$`-tokens unchanged. - app-shell: dataset bridge tables keyed on the protocol ids, plus `icontains` <-> `$icontains`; the Studio widget's local SPEC_TO_FB table is removed (the builder folds at its read boundary). - plugin-view: CANONICAL_TO_BUILDER (now an identity) removed. - i18n: `filterBuilder.operators.*` re-keyed in all ten packs, values untouched. Stopped because two consumers OUTSIDE the claim's file surface need edits: app-shell `drillUrlFilters.ts` (it reuses the `isNull` / `isNotNull` label keys) and app-shell `ObjectView.tsx` `sanitizeViewOverride` (raw value-less lookup against a set whose builder half no longer carries the deprecated ids). Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…r the protocol operator ids
Completes the dropdown half of objectui#9306 on top of the checkpoint.
- app-shell drillUrlFilters: NULL_FILTER label keys follow the re-keyed
`filterBuilder.operators.is_null` / `is_not_null` (the drill chip rendered
the raw key otherwise).
- app-shell ObjectView sanitizeViewOverride ONLY: the row's operator is folded
through the spec's normalizeFilterOperator before the value-less lookup, so
a stored `{ operator: 'isEmpty', value: '' }` override row is kept on read.
- components: normalizeFilterBuilderOperator keeps the spelling when the
spec's normalizer answers a non-string (Object.prototype keys).
- Stale prose: viewFilterFold VALUELESS_FILTER_OPERATORS comments (set
unchanged), core filter-converter + 8976 test, types 9559 corpus label,
schema-catalog 7561 CORRECTION arm.
- Pins: in-surface parity pins re-keyed to the protocol ids; new
filter-builder-protocol-ids-9306 (vocabulary, type-level pin, read-side
fold, camelCase load-and-save-back) and the app-shell 22-id census; live
grid and view-config census legs beside their readers.
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ell, plugin-view, i18n Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ter-builder-protocol-ids
|
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
|
…ocol ids (objectui#9306) Four pending changesets publish in the same release and state present-tense facts about the builder's ids that objectui#9306 makes false: 7561, 9302, 9359 and 8748. Each gains one "Superseded in this release by objectui#9306" paragraph after its history; frontmatter and history sentences untouched, following the objectui#10465 precedent. Also: the objectui#9306 changeset no longer lists containsCaseInsensitive among the spec's deprecated aliases (the spec's table has no row for it), and two test docblocks stop calling it an opt-in builder id. Prose only. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ 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
|
Part of #9306. ⛔ This PR does not close it: ruling item 4's components half stays open on the card (see "Why objectui#9306 stays open" below).
Clause-②: yes
The
FilterBuilderdropdown now emits the protocol's operator ids: the twenty members of@objectstack/spec'sVIEW_FILTER_OPERATORS, spelled the way the spec spells them, plus the opt-inexists/notExists, which stay unfolded under objectui#9559 ruling B. The old camelCase ids are the spec's deprecated alias form (objectui#7993). They are accepted on read and rewritten on write. The builder folds a stored spelling at its read boundary, through the spec'snormalizeFilterOperatorplus one local row the spec's alias table lacks:containsCaseInsensitive→icontains, tracked as objectstack-ai/objectstack#20092. The author's next edit then writes the canonical id. Opening a stored filter writes nothing.This is the dropdown half. The mirror half landed as objectui#10465 (
1779e8dee).Why objectui#9306 stays open (seat note)
The
domain:uiseat #1 (session_01BA3nKVUwKQJf8DBxrSVtNC) changed line 1 from a closing keyword to a plain reference. The rest of this body is the dev's, unchanged.Ruling
5651809976item 4 is an acceptance criterion of objectui#9306: 「objectui#6349's two parked name-authority rows converge on the@object-ui/typesdeclaration withoperatortyped as the protocol set」. objectui#10465 delivered the types half. This PR does not touch the components half:packages/components/src/custom/filter-builder.tsxstill declares its ownFilterBuilderCondition { operator: string }andFilterGroup;FilterBuilderOperatorexports remain:@object-ui/typeswith the protocol's 20, and@object-ui/componentswith those 20 plus the opt-inexists/notExists.Converging the components declaration on the types one "with
operatortyped as the protocol set" would refuse the opt-in pair that the later, ratified ruling B keeps in the dropdown. The seat records that on the card instead of choosing here. Everything else this card asks for (items 1–3 and 5) is in this PR.What changed, by consumer
defaultOperators, the buckets,VALUELESS_FILTER_BUILDER_OPERATORS, the label defaultsnormalizeFilterBuilderOperator(new export) folds each row when a group arrives. It feeds the arity, reconcile, mounted-trigger and value-input folds.OPT_IN_OPERATORSicontainsis no longer opt-in (seat decision on the card; see below).exists/notExistsstay opt-in.condToMongo,kvToCondition,TEXT_COMPARAND_OPERATORS,FILTER_CONDITION_EXTRA_OPERATORS$-tokens are unchanged.OP_TO_MONGO/MONGO_TO_OP/VALUELESS_TO_MONGOicontains↔$icontains.widgets.tsxSPEC_TO_FBdrillUrlFilters.tsNULL_FILTERlabel keysfilterBuilder.operators.is_null/is_not_nullObjectView.tsxsanitizeViewOverridenormalizeFilterOperatorbefore the value-less lookup, the same both-sides fold objectui#9302 / #9359 used. A stored{ operator: 'isEmpty', value: '' }override row is kept.CANONICAL_TO_BUILDERspecToBuilderOperatorreturns the canonical member.filterBuilder.operators.*icontainsleavesOPT_IN_OPERATORS. Seat decision on the card, executing the plan that docblock records. Once the dropdown's id wasicontains, plugin-list's offered-equals-expressible spine measured it as expressible on both list dialects but still withheld:VALID_AST_OPERATORShas it,ViewFilterRuleSchemaaccepts it, andmapOperatorhas an arm for it. It is now offered on the text bucket to every consumer.FilterConditionFieldkeeps writing$icontains, andcontains/icontainsstay two operators (objectui#7379).The 22-id census: before and after, every consumer
I measured once, the same way for both trees. On the base tree (
bc4efba73) each consumer ran over the 22 camelCase rows; on this branch it ran over the 22 ids those rows became. Consumers:condToMongoand itskvToConditionread-back, the datasetgroupToConditionand itsconditionToGroupread-back, the saved-view fold, the live grid'sconvertFilterGroupToAST, the override recovery pass, and the view-config reader. That is 22 × 8 cells.containsCaseInsensitiveagainst branchicontains, on the dataset bridge (write and read-back), the saved-view fold and the live grid. Those are the consumers that never offered the case-insensitive contains.isFilterASTrejects. The new id produces$icontains/ a validicontainsrule / anicontainsnode. These are new rows, not changed ones.condToMongostores the identical predicate for all 22. So does the override pass (keep / drop).The permanent pins hold this:
app-shellfilter-builder-protocol-ids-census-9306.test.ts: every id's stored predicate incondToMongoand the dataset bridge, read-backs included, plus fold and override equivalence for both spellings.plugin-listandplugin-vieweach get a 22-row leg beside their readers.componentsfilter-builder-protocol-ids-9306.test.tsx: the vocabulary equals the protocol set, and a type-level pinFilterBuilderOperator=ViewFilterOperator | 'exists' | 'notExists'. It also covers the 22-row read-side fold, and a stored camelCase group that loads with no write and saves back canonical on the next edit.Ablations
All three ran under
ablation-replace(WRAP mode) atf5134f30a. In each, the anchor went ×1 → ×0 on disk, the blob changed, and the restore was checked as blob == HEAD with an emptygit diff HEAD. The subjects load through the vitest alias tosrc, so no dist leg is owed.condToMongo'sgreater_than_or_equalarm deleted:Tests 3 failed | 192 passed. The census reportedgreater_than_or_equalas storing a different predicate (the row fell to thedefaultequality), the census read-back failed, and so did the fields round trip.normalizeGroupOperatorsreturns its input):Tests 1 failed | 30 passed. The failing test is "saves back: the author's next edit writes every row's CANONICAL id".sanitizeViewOverridefold removed:Tests 11 failed | 143 passed. Every stored camelCase value-less row (isEmpty,isNotEmpty,isNull,isNotNull,isnull, and the legacy triple) was dropped.Tests and gates
All results are from the repo root through the shared verify lock, at
2f87c03ec(this branch withorigin/mainmerged in). Every run exited 0.Test suites:
vitest run packages/components/:300 passed | 1 skipped (301)vitest run packages/fields/ packages/plugin-view/ packages/plugin-list/:334 passed | 1 skipped (335)vitest run --shard=N/3 packages/app-shell/src/views/, N = 1..3: 166 / 166 / 166 files passedvitest run packages/i18n/ packages/types/src/__tests__/ packages/core/src/utils/ examples/schema-catalog/test/plus the i18n call-site, dead-key, en-drift, designer-table, defaults-population and placeholder-spelling script suites:433 passed (433)Type-check after building the app-shell closure (
turbo run build --filter=@object-ui/app-shell^... --concurrency=2, 28 tasks):run type-check(tsc --noEmit && tsc -p tsconfig.test.json) exited 0 on types, core, i18n, components, fields, plugin-list, plugin-view and app-shell.--listFilesOnlyshows the new pins inside the test programs.Reverse check against the rebuilt dist: in a scratch file in
fields,FilterBuilderOperator = 'greaterOrEqual'gives TS2322. The control, with that line removed, exits 0. The file was deleted.ESLint over the 45 touched lintable files, per rule, base against head (at
f5134f30a; the merge touched none of the 45):no-explicit-any: 259 → 259no-unused-vars: 3 → 3exhaustive-deps: 10 → 10set-state-in-effect: 5 → 5react-refresh/only-export-components: 39 → 40. The newnormalizeFilterBuilderOperatorexport sits in a component file that already exports ten helpers.Gates, all exit 0:
check-changeset-presence: 1 changeset for 8 released packagescheck-changeset-no-majorcheck-changeset-overwrite: 0 modifiedcheck:new-line-citations: 0 newcheck:i18n-keys: the dynamic family is read fromdefaultOperators, so the re-key is seencheck:control-bytescheck:changeset-claims(report-only): I read all 9 flagged paragraphs, and none is falsified by this change.Declared deviations
ObjectView.tsx: besidessanitizeViewOverride, the only edit is one added specifier,normalizeFilterOperator, on the file's existing@objectstack/spec/uiimport line. The fold needs it. It is hunk-disjoint from objectui#10035's region, and that card's PR objectui#10494 has already merged.normalizeFilterBuilderOperatorguards the spec's answer. If the spec returns a non-string, the builder keeps the original spelling. Measured:normalizeFilterOperator('constructor')returns theObjectfunction, andtoString/hasOwnProperty/valueOfreturn their prototype members, although the function is declared to return a string. The builder now STORES the fold's answer, so without the guard such a row would lose its operator on serialisation. The defect itself is upstream and reported, not patched here in any other way.SPEC_TO_FBandCANONICAL_TO_BUILDERare removed rather than re-keyed. Re-keyed, they would have been a hand-kept copy of the spec's fold and an identity table.FILTER_BUILDER_IDSliteral in the types 9559 test is namedDROPDOWN_IDS. It is relabelled as the pre-objectui#9306 corpus rather than rewritten, because those ids are exactly what old stored filters carry.Acceptance notes (outside this change's surface, not edited)
carrier: seat.
plugin-listListView.tsx, held by objectui#10326. Two stale docblock sentences:LIST_VIEW_EXTRA_OPERATORSdocblock says neither list dialect has a case-insensitive contains. That was already false since spec 17.1.0, and the toolbar now offersicontains.VALUELESS_FILTER_BUILDER_OPERATORS_CANONICALdocblock calls the builder's set camelCase.Both are still correct in behaviour.
carrier: seat.
content/docs/components/complex/filter-builder.mdx(draft PR objectui#10515) lists operators by English label only. It could state the protocol-id vocabulary and the read-side alias rule.carrier: seat. These pending changesets name the builder's ids in their camelCase spelling in the present tense, and publish into the same release:
7561-filter-builder-operator-trigger-label,9302-filter-builder-valueless-canonical-fold,9359-list-ast-valueless-canonical-fold,9559-filter-operator-schema-protocol-setand8748-icontains-empty-comparand. An in-release supersession note is the precedent objectui#10465 used.For the seat to file in objectstack (class a):
normalizeFilterOperatorreturns non-strings forObject.prototypekeys. The probe is in deviation 2.Generated by Claude Code