Skip to content

refactor(data-objectstack): rename the spec-named filter-operator fold (objectui#7265 data-objectstack slice) - #9527

Merged
os-tesla merged 1 commit into
mainfrom
claude/issue-7265-data-objectstack-filter-operator
Sep 14, 2026
Merged

os-tesla merged 1 commit into
mainfrom
claude/issue-7265-data-objectstack-filter-operator

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Part of objectui#7265 — the @object-ui/data-objectstack slice of the check:spec-symbols DEBT burn-down. One group remains (@object-ui/plugin-detail), so this carries no closing keyword by design.

What moved

packages/data-objectstack/src/index.ts declared a module-local normalizeFilterOperator — the exact name @objectstack/spec/ui exports, and which this monorepo's view layer imports from there (viewFilterFold, filter-converter, ListView, UserFilters, the FilterBuilder). It is now toAstFilterOperator; rule 1's DEBT block is regenerated mechanically with --ledger.

Route: RENAME — and the measurement that refused BIND

Re-measured against the RESOLVED pin, @objectstack/spec@17.4.0 (declared range ^17.2.0; "same behaviour" is a statement about a version exactly as "byte-identical" is).

The two functions take the same input and land in different vocabularies:

input spec's normalizeFilterOperator this package's fold
eq equals the equals symbol
equals equals the equals symbol
greater_than greater_than the greater-than symbol
before before the less-than symbol

The spec's folds an authored spelling to the canonical VIEW vocabulary so ViewFilterRuleSchema's enum can judge it (it is the z.preprocess step on ViewFilterRuleSchema.operator). This package's translates the same input into the server's filter-AST SYMBOLS. Same input, different codomain — so binding would have changed what goes on the wire for most of the operators a stored view can carry. A function also has no derive-in-place form: rule 1 records every function declaration with derived: false, which leaves exactly three exits (delete and import, rename, waive). RENAME removes the collision instead of excusing it, and the declaration was never exported, so renaming costs nothing outside the file.

What the measurement overturned — the ?? op tail

The dispatch's hypothesis was that the spec's version REFUSES where this one is lenient. Measured, it is the other way round, in both halves:

  • The lenient tail is COMMON ground, not the divergence. Both hand an unrecognised STRING back unchanged: normalizeFilterOperator('totally_unknown') returns 'totally_unknown', and so does this package's table lookup. "The spec refuses instead" is not a difference that exists.
  • The tail DOES diverge — on the non-string arm, and the spec is the lenient one. The spec's body ends return op as string, so a non-string comes straight back (42 returns 42, null returns null). This package returns null there, and objectFilterEntryToAST reads that null one line later into a MalformedFilterError. Binding the spec would therefore have WIDENED what this adapter accepts onto the wire — a number in the operator slot of a tuple it is about to send, instead of a 400-shaped refusal.

That refusal is already pinned end-to-end, through the real adapter and both find() routes, by the operator: 42 case in packages/data-objectstack/src/filter-entry-translation.test.ts. Clause-② stays no: nothing this PR does changes what the package sends or what it accepts as an operator — the ?? op tail, the null arm and every row of FILTER_OPERATOR_ALIASES are byte-for-byte unchanged. The trip-wire was checked, not assumed.

The INTERMEDIATE reading (site edit, not a ledger edit)

The gate was run after the site edit and before regenerating the block. Real output:

❌  a spec-named symbol is hand-written, not derived:

    • @object-ui/data-objectstack lists 1 symbol in DEBT that no longer collide — `normalizeFilterOperator`.
      Delete them from scripts/check-spec-symbol-derivation.mjs (`--ledger` regenerates the
      block) so the names cannot be re-forked silently […]

The gate's trailing clause is elided on purpose: it spells a closing keyword immediately before this card's number, and GitHub's linked-issue parser does not read sentences or spare fenced blocks — quoting it verbatim here risks closing a card that still has a group left. Read the full wording in the gate's source (its stale-entry message). Seat attribution for this run, as prose so it survives an edit: session_011QreXiyMEqKLN4U5daMPVa.

exit 1. That message names the entry as stale — which is only reachable if the declaration left the tree first.

Ledger, before and after

Banner before (at bbc9dc3): 18 declared dialects, 2 untriaged collisions in 2 packages.exit 0
Banner after: 18 declared dialects, 1 untriaged collision in 1 packages.exit 0

--ledger re-run after the splice is byte-identical to what was spliced in (idempotent). --claim-ledger regenerates CLAIM_DEBT byte-identically across this change — the declaration that moved carried no spec-alignment claim, so rule 2's block is untouched.

The stale figure, deleted rather than re-derived

The DEBT_ISSUE note carried a figure written by an earlier slice and overtaken by every slice since: it said how many entries objectui#7265 had burned out of each block. Per AGENTS.md #9 it is deleted, not re-derived to a new number. The sentence now names the routes (BIND / RENAME / ALLOW) and the instruments (--ledger, --claim-ledger, and the run banner) and carries no count at all.

One extra deletion, declared: the same note's preceding clause read its 18-entry block is live, and the sentence I was asked to rewrite ended only the COUNT above moved — pointing straight at it. Leaving the adjective while writing "no count is written here" would have made the replacement contradict the line above it, so 18-entry went too. It is its block is still live now; the argument (the anchor is closed while the block is not empty) is intact.

Pins that FAIL before and pass after

Both legs were run from the committed state, mutation proved on disk by marker counts and blob hashes, restored with git checkout HEAD -- PATH and confirmed by an empty git diff HEAD. Neither leaves a file behind.

Leg 1 — the site. Restored packages/data-objectstack/src/index.ts from the pinned base commit (marker counts moved toAstFilterOperator 2 to 0, function normalizeFilterOperator 0 to 1; on-disk hash changed):

GATE EXIT=1
    • @object-ui/data-objectstack declares 1 spec-named symbol the spec already owns:
        function `normalizeFilterOperator`  (exported by `@objectstack/spec/ui`)

PIN EXIT=1
 Tests  1 failed | 14 passed (15)
 × rule 1 no longer sees `normalizeFilterOperator` — the declaration is `toAstFilterOperator` now

Exactly one assertion fails, and it is the site assertion. Green on the branch: 15 passed (15).

Leg 2 — the figure. Put the deleted figure back:

PIN EXIT=1
 × carries no entry count at all
AssertionError: the `DEBT_ISSUE` note has a ledger size written into it again. …
  expected [ '18-entr', 'two entr' ] to deeply equal []

It catches BOTH re-introduced counts, which is what makes it a ratchet against a helpful re-derivation rather than a spelling pin.

Ratchets left behind

  • The name, in this package's own spec-symbol file packages/data-objectstack/src/spec-symbol-batch6.test.ts — appended to its existing RENAMES table rather than duplicated into a new file. Both directions: the spec must not come to own toAstFilterOperator, and it must still own normalizeFilterOperator (if it stops, the rename's reason is spent and the plain name can be taken back).
  • The site, the block and the figure, in scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts — the sibling shape of the core / app-shell / types / components pins. Its controls include one this slice had to add: a fixture that imports the spec's function and re-declares a wrapper under the same name is STILL flagged, which is the assertion that records why a function's route set is smaller than a type's.
  • The behaviour, in packages/data-objectstack/src/filter-operator-ast-parity.test.ts, beside the table it is about. The divergence set is DERIVED from the spec's own vocabularies and enumerated, never counted, with a lit control (contains agrees on both sides) so "they differ" cannot be vacuous.

Verification

  • pnpm exec vitest run packages/data-objectstack/ packages/plugin-view/ scripts/__tests__/Test Files 265 passed | 2 skipped (267), Tests 5910 passed | 2 skipped (5912).
  • pnpm --filter @object-ui/data-objectstack --filter @object-ui/plugin-view run type-check — exit 0, both scripts echoed. This package's tsconfig.json compiles its test tree (confirmed with --listFiles: both edited test files are in the program), so the pins are checked by tsc --noEmit too.
  • pnpm run type-check:scripts — exit 0 (covers the new scripts/__tests__ pin).
  • Gates: check:spec-symbols exit 0, check:changeset-presence exit 0, changeset:check exit 0, check:new-line-citations 0 new citation(s), check:control-bytes OK, check:test-path-roots OK.
  • check-governed-queue-guard --test over every changed path: NOT GOVERNED — none matched. Ordinary review route.
  • Lint, narrowed and measured. eslint --no-inline-config --format json over the six changed files reports 6 files linted, 4 errors / 130 warnings, all in packages/data-objectstack/src/index.ts. The same command over that file at the base commit reports the same 4 errors / 130 warnings, same four no-console rules, shifted by exactly the 30 lines this PR adds: zero new findings. Type-aware linting is not configured in eslint.config.js (no parserOptions.project / projectService), so this diff cannot move the verdict of any file it does not contain. The repo-wide pnpm lint is CI's run, not this one.

Acceptance notes

check:changeset-claims (report-only) flagged four pending changesets whose bodies name scripts/check-spec-symbol-derivation.mjs. Read, as the gate asks. This PR falsifies none of them — three describe derivation forms and rule 2's comment attachment, all still accurate. The fourth, .changeset/6286-explain-batch-cap-from-spec.md, says "both of its scanners skip non-exported declarations"; that sentence is already false, and was made so by objectui#6291 widening rule 1, not by this branch. It is another card's pending body and it publishes verbatim into the CHANGELOG — reported rather than edited here.

Noted, not filed:

  • scripts/__tests__/check-spec-symbol-derivation.test.ts carries a comment saying isContextToken and normalizeFilterOperator "are the live instances … both DEBT entries today". The first half went false with the @object-ui/core slice; this PR takes the second. Not repaired here on the same reasoning the components slice declined the figure — another slice's paragraph, not this slice's file moment. Taker: the @object-ui/plugin-detail slice, which empties the block and inherits the whole sentence.

Departure from the dispatch, declared

The dispatch's file surface named the site, the gate script and the parity ratchet. One file outside it is in this diff: packages/plugin-view/src/config/__tests__/view-operator-builder-parity.test.ts, whose header cited data-objectstack's normalizeFilterOperator by symbol name. A rename that leaves dangling citations is an incomplete rename, and AGENTS.md #11's whole thesis is that the person who moves the thing never sees the citation — this seat did see it. One comment, no behaviour, and plugin-view is in the verification set above because of it.

Do not enqueue; draft by instruction, and auto-merge is deliberately not enabled.


Generated by Claude Code


Generated by Claude Code

…d (objectui#7265 data-objectstack slice)

`packages/data-objectstack/src/index.ts` declared a module-local
`normalizeFilterOperator` — the exact name `@objectstack/spec/ui` exports and
most of this monorepo imports. Renamed to `toAstFilterOperator`, the codomain
that makes it a different function; the ledger in
`scripts/check-spec-symbol-derivation.mjs` is regenerated with `--ledger`.

RENAME rather than BIND, decided by measurement at the RESOLVED 17.4.0 pin: the
spec's folds an authored spelling to the canonical VIEW vocabulary so
`ViewFilterRuleSchema`'s enum can judge it, this one translates the same input
into the server's filter-AST symbols — `equals`/`before` against `=`/`<`.

The measurement overturned the seeding note in both directions. The `?? op` tail
is common ground, not the divergence: both hand an unrecognised string back
unchanged. The tail does diverge on the NON-string arm, and the other way round
— the spec returns it verbatim where this package returns `null`, which
`objectFilterEntryToAST` reads into a `MalformedFilterError`. Binding would have
widened what this adapter accepts onto the wire.

Behaviour unchanged; the declaration was never exported. The stale entry-count
figure in the gate's `DEBT_ISSUE` note is deleted rather than re-derived
(AGENTS.md #9), and the sentence now names `--ledger` / `--claim-ledger` and the
run banner instead.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3062.6 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-CRztz5Ax.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 544.93KB 130.50KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.98KB 59.97KB
fields (index.js) 249.20KB 62.88KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.99KB 14.24KB
plugin-charts (index.js) 71.33KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 252.48KB 65.61KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.71KB 34.16KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.83KB 57.97KB
plugin-kanban (index.js) 46.63KB 14.53KB
plugin-list (index.js) 112.67KB 27.68KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 84.36KB 20.78KB
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) 99.04KB 32.62KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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.93KB 1.49KB
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 (expression.js) 0.20KB 0.18KB
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.73KB 2.28KB
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 (strict-authoring-face.js) 14.04KB 5.36KB
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

@os-tesla
os-tesla marked this pull request as ready for review September 14, 2026 21:07
@os-tesla
os-tesla added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 63dc4f7 Sep 14, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-7265-data-objectstack-filter-operator branch September 14, 2026 21:27
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