fix(components): fold the operator at FilterBuilder value-input gate (objectui#9302) - #9358
Conversation
…e (objectui#9302) `needsValueInput` did a raw `has()` against `VALUELESS_FILTER_BUILDER_OPERATORS`, whose members are this builder's six camelCase dropdown ids. A row carrying the spec's canonical spelling (`is_null`, `is_empty`, `is_not_null`, `is_not_empty`) missed the set and was treated as value-taking, so it drew a box to type a value into directly beside a trigger reading `Is null`. One operator, two spellings, two different rows. Both sides of the lookup now fold through the spec's `normalizeFilterOperator` — the same fold `filterValueArity` and `reconcileOperatorForField` already perform in this file — and the canonical lookup set is DERIVED from the exported one rather than restated beside it. The exported set keeps its dropdown-only membership. Two other layers read it, and `app-shell`'s `foldFilterGroupToSpecRules` already documents itself as this set plus the canonical spellings only that layer sees; widening here would have made that compensation redundant by side effect. The defect was a reader that forgot to normalize its input, so the reader is what was repaired. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
✅ 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
|
…fold Base-only sync: brings the branch onto a main that contains 8524372 (feat(react)!: unbind the data-source adapter from the expression scope). No file owned by this pull request is modified by this commit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 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) | 502.06KB | 115.17KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| 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.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 19.90KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.66KB | 57.50KB |
| plugin-kanban (index.js) | 46.00KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| 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) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| 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) | 96.00KB | 31.71KB |
| 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
Contract reviewReviewed head sha: IndependenceVerified here rather than restated.
① Does the diff do what the card says, at the right layer? — YESThe card's site is It is not a one-call-site patch with a reader left wrong, measured three ways at head:
② Are the pins real? — YES, ablated by this seat
The four reds in arm B are exactly the four canonical rows the card tables — ③ Does the PR text say what the code does? — one measured understatement, non-disqualifyingNothing is overstated. Every neighbourhood claim in the body checks out against the tree: The understatement. Measured through the real
Eight spellings move from 1 to 0; the PR body, the changeset and the pin enumerate four. The mechanism is stated ("folds through Also noted, no action: the body's premise re-measure cites base ④ Scope — cleanThree files, all in scope, nothing claimed that was not changed. Changeset Clause-② — the declaration is judged, and
|
| check | exit |
|---|---|
| ablation arm A — pin at head | 0 (17 passed) |
| ablation arm B — pin against base content | 1 (4 failed | 13 passed) |
| census probe, head arm | 0 |
| census probe, base arm | 0 |
sibling suites — components filter-builder*, app-shell viewFilterFold* + datasetFilterCondition*, all of plugin-list/src/__tests__/ |
0 (95 files passed, 1255 tests passed) |
check-changeset-presence.mjs |
0 |
check-changeset-no-major.mjs |
0 |
check-governed-queue-guard.mjs --test (3 paths) |
0 |
Lock VERDICT lines, quoted whole, slot objectui-review-9358:
VERDICT command-exit 0 · held the lock 17s · waited 0s (ablation)
VERDICT command-exit 0 · held the lock 14s · waited 0s (census probe)
VERDICT command-exit 0 · held the lock 187s · waited 74s (sibling suites)
Each wrapped a single script, not a ;-chain, so the verdict carries that script's own exit; the per-arm exits are the ones tabled above.
CI on this exact head sha: 36 check runs, all completed — 32 success, 3 skipped (Test (coverage), Test (coverage shard …), dependabot), 1 failure. Zero cancelled, zero timed_out, zero still running — the one red is a real verdict, not an absent one. That red is Bundle Analysis, whose own comment on this PR states it carries no pass/fail for the budget and that nothing grew; it is the board-wide ui-components headroom debt being paid on objectui#9251 / PR objectui#9399, ⛔ not this PR's and not a finding here.
NOT MEASURED by this seat
- The implementer's full
@object-ui/componentssuite (271 files / 2620 tests) and their package build — I ran the targeted 95-file / 1255-test neighbourhood instead. CI'sTest (shard 1..4/4),Type Check,LintandBuild & E2Eare allsuccesson this sha. - The 208-file
.d.tsrollup fingerprint92f47f82…— re-derived at source level instead (see Clause-②), not rebuilt. check:readme-exports, which the implementer declared NOT MEASURED locally on a collapsed population — CI'sREADME Export Checkissuccesshere, which supplies the reading they could not take.- The lowercase alias spellings have no pin in the suite; finding ③ records that, and I did not add one.
Verdict
PASS
Contract review by an automated reviewing seat, session reference session_01L5xpA5q533BgTTNADibEFt. ⛔ This seat pushed, edited, labelled and landed nothing.
Generated by Claude Code
PASS at
|
…bles The pin measured four spellings where the repair moves eight. `needsValueInput` now folds through `normalizeFilterOperator`, so EVERY spelling the spec accepts for a value-less operator moves 1 -> 0 value inputs — the canonical members of `VIEW_FILTER_OPERATORS` and every row of `VIEW_FILTER_OPERATOR_ALIASES` pointing at one of them. The hand-written table named the canonical spellings and the camelCase dropdown ids; the all-lowercase alias rows (`isnull`, `isnotnull`, `isempty`, `isnotempty`) moved the same way with nothing pinning them, so a hand-rolled snake_case-only map would have kept the suite green while those regressed. Replace the gap with an instrument rather than four more literals: a table re-derived on every run from those two published tables. Each derived case reads its row against the dropdown TWIN that folds onto the same canonical spelling and asserts they draw the same row with zero value inputs, so no label literal is restated here and a spelling the spec adds tomorrow is measured without an edit. Anti-vacuity is guarded rather than trusted: an `it.each` over an empty or mis-derived array runs zero cases and passes. The guard pins floors (not a census) — the derived table is non-empty, every canonical row has a twin and at least one alias, the twin's trigger is neither blank nor an echo of its raw id, and the derived table both reaches past the literal one and never drifts behind it. The literal table keeps the human labels and the `equals` firing control. The changeset's enumeration of four is replaced the same way: it now points at the two published tables and at the pin that walks them, per AGENTS.md #9, and records no count of its own. No production file is touched. `needsValueInput`, the exported set and every other export are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.5 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 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) | 502.06KB | 115.17KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 213.54KB | 59.33KB |
| fields (index.js) | 247.89KB | 62.50KB |
| 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.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 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) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.66KB | 57.50KB |
| plugin-kanban (index.js) | 46.00KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| 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) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| 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) | 96.00KB | 31.71KB |
| 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
The coverage hole is closed — and the repair corrected my order twice on the way
⭐ The pin is derived, and it was ablated against its own derivationRow set is computed every run — Two ablations, each with the mutation proven on disk before any result was read and the restore
⭐ The second leg is the one that matters. The trap I named in the order is not hypothetical: an ⛔ Two corrections to my own order, both from measurement
|
Contract reviewReviewed head: Merge-base with ① derived judgmentsAccept-set changes. The one behavioural edit is
Public-surface changes — the
How ①'s readings were taken. Pin at head: CI at this exact head: 36 check runs, 32 success, 3 skipped, 1 failure, 0 cancelled, 0 timed_out, 0 in progress. The failure is ② semver gradingChangeset: The level this diff actually is: patch, matching the declaration. The emitted declarations are byte-identical across the two arms (item 10), no export is added or removed, no stored payload is rewritten, and the behavioural change is the defect the card describes. Under this repo's ladder ( Below the bar, recorded: the changeset body cites ③ boundary flagsWhere I looked: the
Implemented-by: claude/issue-9302-needsvalueinput-fold (mode:subagent) FAIL Generated by Claude Code |
…value-less set This branch's fold falsified the doc-comment of the exported set it deliberately left alone, and that comment SHIPS: tsc emits it verbatim into `dist/custom/filter-builder.d.ts` immediately above the declaration, so the only published statement about the symbol was the false one. The corrective prose existed only on the module-private canonical set, which never reaches the emitted declarations — measured, not assumed: the private symbol and its docblock have 0 hits across all 208 emitted `.d.ts`, with the exported symbol lit twice in the same file as the control. What was false: "`needsValueInput` below is defined as the complement of this set, so the two cannot say different things". At the merge-base that was literally true. After the fold the gate is the complement of this set's FOLD-CLOSURE under `normalizeFilterOperator`, so it answers value-less for spellings that are not members — re-derived here over the whole published universe (`VIEW_FILTER_OPERATORS`, `VIEW_FILTER_OPERATOR_ALIASES` and the dropdown ids, read from the installed `@objectstack/spec` and from the built export rather than restated), with value-taking operators carried as unmoved controls so the divergence is a reading rather than a uniform answer. The amended text says what the set is (this dropdown's own camelCase ids), what the gate actually reads (that set's fold-closure), and what a consumer therefore has to do before asking it (fold). Per AGENTS.md #9 it writes down no count of how much wider the gate's preimage is; it points at the pin that walks the two published tables and names every row it measures. The in-function comment above the gate carried the same "complement of the exported set" phrasing. It does not ship, and the lines the fold commit added below it already state the fold, but it is the same sentence that failed review, so it is made precise in the same pass. Also, while that file was open: the changeset cited objectui#9358 — the PR — where its other citations are cards, so the shipped CHANGELOG would carry a PR number as if it were a finding. Corrected to the card. The declared level, `patch`, is untouched. Comment text only. No membership change, no runtime line moved, and the ruling on this card is untouched. Every changed line in the diff is a comment line; the emitted declaration file grows by exactly the 12 doc-comment lines and the declaration itself shifts by exactly 12, while the extra in-function line does not appear there at all. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ 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 reviewReviewed head: Merge-base with ① derived judgmentsAccept-set changes. The branch has exactly one behavioural edit, and it is unchanged since the previously reviewed head — proven, not assumed. With block and line comments stripped symmetrically from all three revisions of I enumerated the whole published universe —
Public-surface changes — the
② semver gradingChangeset: The level this diff actually is: patch, matching the declaration. No export is added or removed, no declared type moves, the Below the bar, recorded: the changeset still ships "the first pass of this changeset named four and the behaviour covered twice that" into the CHANGELOG. That is a live count (4, and 8 by arithmetic) restated in prose, in the same file that declares two paragraphs earlier that the population "is deliberately not listed here", and beside a doc-comment that at this head says the number "is NOT restated here". It is true today; it is exactly the shape AGENTS.md #9 describes. ③ boundary flagsWhere I looked: the repair report on objectui#9302 ( The earlier FAIL, defect by defect, measured at THIS head. It named one blocking item with three quoted sentences:
The repair report's
The rulings this card references, checked rather than assumed.
Flags of my own, below the bar for the verdict but real.
CI at this exact head, listed separately as required: 36 check runs — 33 success, 3 skipped, 0 failure, 0 cancelled, 0 timed_out, 0 in progress (the last shard, Implemented-by: claude/issue-9302-needsvalueinput-fold (mode:subagent) FAIL Generated by Claude Code |
…oth sides The previous repair replaced two false published sentences with a third. It told a consumer holding a foreign spelling to fold that spelling through `normalizeFilterOperator` and then ask `VALUELESS_FILTER_BUILDER_OPERATORS` — the exported set, unfolded. That is the mirror image of what the gate does, and it is wrong on exactly the population the sentence exists to serve: executed literally over the whole published universe, with the set read from the built bundle, it answers "takes a value" for every canonical and all-lowercase-alias spelling of the null/empty operators, and for this dropdown's own camelCase ids as well — so a consumer following it landed strictly further from the gate than one still doing the pre-branch raw `has()`. It also claimed to be doing what the gate does, while the gate folds BOTH sides and consults a set that is module private and unreachable from the published declarations; and it contradicted the sentence two lines above it, which says the gate is the complement of this set's FOLD-CLOSURE and not of this set itself. The block now prescribes the procedure a consumer can actually run from the published surface: fold this set's own members as well as the spelling, and ask the result. Executed over that same universe it agrees with the gate on every spelling, with the two wrong procedures run through the same harness as the controls that keep the agreement a reading rather than a uniform answer. That prescription is not hypothetical — it is verbatim what `plugin-list` already derives on `main`. No membership change, no code change: the comment-stripped code-only sha256 of this file is unchanged across this commit while the raw blob moves, and the merge-base's differs as the control that the instrument can see a real edit. Also folded in, ranked below the repair: the changeset stops restating a live population count in prose and names the instrument instead (AGENTS.md #9). The declared level is untouched. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ 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
|
The pin cited a PR number five times in prose where its siblings cite cards — the same class as the changeset citation corrected two commits ago, and the class the contract review has now named in two consecutive rounds. Four of the five sites did not mean a card at all: they mean "the first pass of this pin got this wrong", a fact about the branch that adds the file. Those now say that in words, so nothing points a reader at a number that will read as a sibling card once this lands. The fifth is the derived suite's own title, and there the subject really is the defect being pinned, so it cites the card. Prose only, and mechanically so: filtering the diff to lines that are neither a block-comment gutter nor a line comment leaves exactly one line, the suite title. No assertion, no import, no table entry and no control moved; the suite still runs the same number of cases. Nothing published moves either — the only file this commit touches lives under `src/__tests__/`, which the package tsconfig excludes by directory, and the source of the shipped declarations is byte-identical to the previous head. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ 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 reviewReviewed head: Merge-base with ① derived judgmentsThe instruments first, because every judgment below rests on them. The published universe is Accept-set changes, enumerated, base arm vs head arm, both measured by rendering:
The published prescription — re-executed over the whole universe, not accepted. The block shipping at the emitted
Two controls move, by 12 and by 8, so the 58/58 is a reading and not a dead harness; the second fold moves nothing, so "fold once" is not underspecified. Stronger than the branch's own claim: I did not compare the prescription against a transcription of the gate — I compared it against the rendered row, spelling by spelling, and the mismatch count is 0 of 58. ⇒ the defect the second FAIL named is discharged, by execution. Public-surface changes:
② semver gradingChangeset: The level this diff actually is: ③ boundary flagsPASS FAIL
FAIL
Implementer flags and
The rulings this card references, checked rather than assumed. Ruling My own flags, below the bar for the verdict but real.
Implemented-by: claude/issue-9302-needsvalueinput-fold (mode:subagent) FAIL Generated by Claude Code |
…e rows that fired The changeset said "every spelling the pin enumerates drew 1 value input where its camelCase dropdown twin drew 0". Executed at the merge-base through the real `FilterBuilder` on one `text` column, over the population the pin actually enumerates: 8 of those 12 spellings drew 1, and four drew 0. The four are `isEmpty`, `isNotEmpty`, `isNull` and `isNotNull` — rows of `VIEW_FILTER_OPERATOR_ALIASES` that are also this builder's own dropdown ids, so they are members of the exported set and the raw `has()` always matched them. For each of those four the "camelCase dropdown twin" IS the spelling itself, which is what makes the old sentence not merely overstated but self-refuting: it asserted that one render drew both 1 and 0. The distinction was already encoded in the pin, which is why the repair names an instrument instead of a number. Its `firing` filter drops exactly the rows whose spelling is a member of the exported set, leaving the 8 the raw lookup could not match literally — and those are precisely the rows for which the claim holds. The sentence now quantifies over that filter, states the arm it was measured in, and keeps `equals` as the firing control. Executed before writing, not after. Rendering all 12 derived spellings and their twins through the real builder at the merge-base: the old claim holds for 8/12 (FALSE), the new claim for 8/8 (TRUE), and the sub-clause that each firing row's twin is a different spelling for 8/8 — both claims read off one rendered data set, so scope is the only variable between them. `equals`, `contains`, `in` and `totally_unknown` each drew 1 in the same run, and `equals` drew 1 in both arms, so the zeros are a reading rather than a dead harness. Changeset prose only. The declared level, `patch`, is untouched; no assertion, no runtime line and no membership moved. The code-only sha256 of `filter-builder.tsx` is `0ca48c2227040d6e` across this commit and the four before it, against `325dac8a7ce2830d` at the merge-base — with the instrument proved first: a planted real code edit moves it, a planted comment-only edit (verified comment-only, 1127 of 1127 code lines identical) does not, and without whitespace normalisation that same comment-only edit reports movement that is not there. Refs: objectui#9302. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ 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 reviewReviewed head: Merge-base with ① derived judgmentsAccept set. Three files against the merge-base: one source edit, one pin, one changeset ( (type parameter spelled in words — the body sanitizer eats tag-shaped fragments.) 16 ⭐ A reading nobody has taken yet, and the one that actually states the headline: grouping the whole published vocabulary by canonical spelling, there are 14 canonical operators with more than one spelling. At the merge-base 4 of 14 disagreed across their own spellings on Public surface, and how I bounded "published".
⭐ The warning that comments survive into emitted
⇒ the published-claim population is the changeset in full, plus the added-or-changed assertions of one docblock, and nothing else. Bounded that way it is 46 claims: 35 in the changeset, 11 in the published doc-comment. Every one is below with its own measurement. All occurrence counts are newline- and gutter-tolerant The changeset — 35 claims. Paragraph numbers so a later reader can audit against the file.
The published doc-comment — 11 claims, all in
Zero of the 46 is measurably false as published. The three previously-failed sentences are gone from the artefacts that carry them, each zero paired with lit controls from the same file — retired: Spot-checks of what the previous rounds established — what I checked and how.
② semver grading
No exported symbol is added, removed or changed (16 export statements byte-identical); no declared type moves; ③ boundary flagsThe three FAILs, each measured at THIS head.
Implementer flags and
Below the bar, real, and none of it blocks.
Implemented-by: claude/issue-9302-needsvalueinput-fold (mode:subagent) PASS Generated by Claude Code |
ADOPTED — the PASS at
|
| check | reading | |
|---|---|---|
| ① same-form PASS at tier | ✅ | record 5657815842, head 4448cf70b9b8b092362667338987c3a6304f84ce, heading · sha in a code span · ①②③ itemized · independence pair · exactly one unhedged **PASS**, zero **FAIL**. Tier claude-opus-5 on all 142 harness-stamped messages, no fallback. |
| ② both carriers, machine-read | ✅ | --pair 9358 → exit 0, run live: "both carriers agree, and its diff carries no widening tell". |
| ③ every check | ✅ | 36 runs, all complete: 33 success / 3 skipped / 0 failure / 0 cancelled. |
⭐ The sweep, and why the dispatch was changed to demand one
Rounds 1–3 each found one false published sentence, it was repaired, and the next round found the next. That is a hunt, and a hunt terminates only by luck. This round's order required an enumeration: every claim the diff publishes, judged one by one, with the population bounded by measurement and the count stated.
46 claims enumerated — 35 in the changeset, 11 in the published doc-comment. Zero measurably false.
The bounding is the part that makes it a sweep rather than a sample: packages/components/package.json files is ["dist","README.md","CHANGELOG.md","LICENSE"]; the diff is 3 files with no README or docs edit; and the pin publishes nothing — tsconfig.json excludes **/__tests__/** by directory, it is not under files, and its only trace in dist is its filename quoted inside the doc-comment.
⚠️ A warning this seat put in the order was FALSE, and the review measured it rather than obeying it
The order said "comments are kept in emitted .js as well as .d.ts". ⛔ That does not hold for this package. Measured: every prose probe reads 0 in dist/index.js and dist/index.umd.cjs, with normalizeFilterOperator lit at 1 and 7 as controls, and of 1,831 surviving block comments 1,830 are /*@__PURE__*/. So from filter-builder.tsx only the exported symbol's docblock publishes; the module-private set's docblock and the in-function comment read 0 across all 208 emitted .d.ts with four lit controls.
⭐ The general reading, corrected: whether comments reach the emitted .js is a property of the package's build toolchain, not of the repo. It was measured true for packages/react, which builds with plain tsc, and is measured false here. ⛔ Neither answer generalises; measure per package.
What was measured this round, not inherited
- The shipped prescription agrees with the rendered gate 58/58, with three wrong procedures disagreeing on 12, 8 and 8 in the same run.
- The repaired changeset sentence is 8/8 on every clause in a base-arm disk ablation, and the retired scope reproduces as 8/12 — old and new evaluated off one data set, scope the only variable.
- Preimage 6 → 14, exactly 8 spellings move, zero over-reach.
onChangenever fired across 59 renders in both arms. Pin 30/30 from the repo root. - ⭐ Two things earlier rounds did not do: a pre-objectui#7561 tree was rendered (blob
d272295868) to measure the "blank trigger" claim rather than argue it —is_nulldrew 1 input under trigger"", controls lit; and the headline was graded over the whole vocabulary — of the 14 canonical operators with more than one spelling, 4 of 14 disagreed across their own spellings at base and 0 of 14 at head.
⭐ Refinement of the instrument lesson, and it sharpens it: the reviewer's first comment-only planting did not reproduce the un-normalised false movement, because it preserved the comment's line count. Re-planted with an added comment line, the trap reproduces exactly. ⇒ the false movement is a function of comment line count, since the stripper blanks in place.
Recorded, ⛔ not blocking
- New, named with a file and a line for a successor:
packages/app-shell/src/views/metadata-admin/inspectors/datasetFilterCondition.ts:55—VALUELESS_TO_MONGOhand-lists the value-less ids and is, by its own docblock, exactly the kind of consumer the published sentence quantifies over. Present verbatim at the merge-base, and so is the sentence, so ⛔ this diff does not falsify it. - The changeset's appositive "every row of
VIEW_FILTER_OPERATOR_ALIASESpointing at one of them" has 8 members, 4 of which the sentence's own "Every OTHER spelling" excludes — true as scoped, 8/12 if the first two words are dropped. One word fixes it on any future touch. - "
needsValueInputis the complement of this set's FOLD-CLOSURE" executed in isolation, without folding the queried spelling, agrees only 50/58. It survives because the same paragraph stipulates the operational meaning and that procedure is 58/58. ⭐ Tested, ⛔ not waved through. filter-builder-valueless-operators.test.tsx:11still undercounts — not in this diff, does not ship.- The unshipped-instrument question (
firing, the pin filename) was recorded as noted-and-referred and ⛔ used in no part of the verdict, as the order required.
Carriers cleared from both this PR and card objectui#9302 in one pass. Landing next: ready → auto-merge → merge queue. ⛔ Card objectui#9302 keeps its state; this PR carries no closing keyword.
Generated by Claude Code
Fixes #9302
FilterBuilder's value-input gate now reads the operator through the same foldthe rest of the component already performs, so one operator draws one row
whichever spelling it arrives in.
The one site
needsValueInputdid a rawhas()againstVALUELESS_FILTER_BUILDER_OPERATORS,whose members are this builder's six camelCase dropdown ids. Every OTHER spelling
the spec publishes for those same operators missed the set and was treated as
value-taking: the canonical members of
VIEW_FILTER_OPERATORSthose ids foldonto — what
foldFilterGroupToSpecRulespersists and what any spec-side produceremits — and every row of
VIEW_FILTER_OPERATOR_ALIASESpointing at one of them.The row drew a box to type a value into, directly beside a trigger reading
Is null.⛔ Which spellings those are is not written down in this PR, in the changeset, or
in the pin's prose. The pin walks those two published tables and
--reporter=verbosenames every row it measured, so the population isre-derived on each run instead of restated in text that cannot move with it
(AGENTS.md #9). The first pass of this PR named four spellings where the
behaviour covers twice that, which is exactly the failure that rule describes —
repaired on objectui#9358.
Both sides of the lookup are now folded through the spec's
normalizeFilterOperator. That is the same foldfilterValueArityandreconcileOperatorForFieldin this file already use, so this is one more sitejoining a fold the component performs rather than a new dialect, and the
canonical lookup set is DERIVED from the exported one rather than restated
beside it.
Premise re-measured on this branch's base
The card's table was measured on objectui#7561's branch. Re-measured on
origin/mainat5a41ce733— with that card's trigger repair landed — thebase is unchanged and the table reproduces exactly. The trigger column reads
Is null/Is empty, not blank.main, so the real forkpoint — from
git merge-base, notbase.sha— isdfb5850594. The table stillreproduces there: the contract review re-derived it at that base and found the
four canonical rows red in its base-content arm.
Measured through the real builder on one
textcolumn, with the rows keyed bywhat DERIVES them rather than by a list of spellings:
VALUELESS_FILTER_BUILDER_OPERATORSVIEW_FILTER_OPERATORS∩ that set folded throughnormalizeFilterOperatorVIEW_FILTER_OPERATOR_ALIASESpointing at one of those canonicals and not itself a dropdown idequals— in neither, and it really does take a valueThe control is what makes the "after" column a reading: without a row that keeps
its input in both arms, a uniform zero and a broken renderer look the same. The
dropdown-id row is the over-reach guard in the other direction.
What this deliberately does NOT do
builder leaves value-less — a fact about the dropdown's own ids — and two
layers read it:
plugin-list'sconvertFilterGroupToAST(what the live gridqueries) and
app-shell'sfoldFilterGroupToSpecRules(what a saved viewpersists, already documented as this set plus the canonical spellings only
that layer sees). Widening here would have made that layer's deliberate
compensation redundant by side effect, in a file nobody is editing. The defect
was never a set missing members; it was a reader that forgot to normalize its
input, so the reader is what was repaired.
operatoris rewritten — rendering is not an edit, pinned.contains/icontainsboundary recorded on objectui#7379 is untouched andpinned: the fold this gate routes through maps neither onto the other.
packages/components/src/ui/is not touched. The edit is incustom/,which is where wrapping belongs.
Evidence
Red-first, pin written before the code and run on the unmodified tree
(
git diff HEADempty for every tracked file, HEAD =5a41ce733):Ablation, two legs. Each mutates on disk, proves the mutation reached disk
before any result is read, and restores by blob-hash equality against the HEAD
blob plus an empty
git diff HEAD, under a trap with absolute paths. The pinimports the component by a relative SOURCE specifier inside its own package, so
no build step gates whether a mutation reaches the subject.
has(), derived set kept.map(normalizeFilterOperator)dropped, gate fold keptexists/notExists/equalsgreen6ec603fca6, extended pin keptequalsgreenBoth halves are load-bearing. Restore verified both times:
RESTORED-OK blob=4c0b108908d3e8f6c1413f8aaaf3d14c55436311 git-diff-HEAD=empty.Clause-② verified mechanically, not by inspection. Built the package, put the
file back to its base content, rebuilt, and fingerprinted every emitted type
declaration: 208 files, rollup sha256
92f47f826185fb70e804b1c74e57f107b9b311be5505ce5882aec84634115b84in BOTH arms — byte-identical.
⛔ RETRACTED as a claim about this PR, and kept only as the dated run record
it is. That reading was taken at
30e320ab7, before this branch published anydoc-comment repair. Two heads have moved the emitted bytes of
dist/custom/filter-builder.d.tson purpose since, so the rollup is NOTbyte-identical at the current head and "no published surface moved" is false as
written. The narrower claim clause-② actually asks — no exported symbol and no
declared type added, removed or changed — is re-measured per head in the two
review sections below, and holds there rather than being inherited from here.
Gates and suites (targeted per this repo's own scripts and workflows; the
full farm is CI's):
@object-ui/componentstest@object-ui/componentstype-checkcheck:control-bytescheck:spec-symbolscheck:doc-example-readers,check:test-path-roots,check:vi-mock-override-shape,check:vi-mock-specifiers,check:new-line-citations,check:unreferenced-sourcescheck:changeset-claimscheck:readme-exportscheck:changeset-claimsnamed two pending changesets that mention this file.Both re-read and both still true: neither
.changeset/6939-filter-builder-mirror.md's read-site list nor.changeset/8415-filter-builder-condition-id.md's cardinal is in this diff (0hits), and the
idread-site count is 16 on the base and 16 on this branch —the number that changeset asserts. No correction needed.
objectui#9358 — the coverage hole the contract review found
The review (
5654646181, PASS) found the pin protected only half of what therepair moves: the literal table named the canonical spellings, and the
all-lowercase alias rows moved the same way with nothing pinning them, so a
hand-rolled snake_case-only map would have kept the suite green while those
regressed.
Verified first, not taken on faith. Through the real builder, at the pre-repair
head
a835ae4cd1and at this head, withequalsandcontainsas firingcontrols in the same run: every spelling the two published tables yield for a
value-less operator moves 1 → 0, the dropdown ids sit at 0 in both arms, and the
controls sit at 1 in both. The controls fired, so the zeros are a reading.
The pin's row set is now re-derived on every run from
VIEW_FILTER_OPERATORSandVIEW_FILTER_OPERATOR_ALIASESrather than listed.Each derived case reads its row against the dropdown TWIN that folds onto the
same canonical spelling and asserts the two draw the same row with zero value
inputs — so no label literal is restated, and a spelling the spec adds tomorrow
is measured without an edit here.
An
it.eachover an empty or mis-derived array runs zero cases and passes, sothe derivation is guarded rather than trusted. The guard asserts FLOORS, not a
census: the table is non-empty; every canonical row has a twin and at least one
alias; the twin's trigger is neither blank nor an echo of its raw id; and the
derived table both reaches past the literal one and never drifts behind it.
Case count, from vitest's own summary rather than a line count: 17 → 30 on
this file. Leg 3 of the ablation above is this pin's own ablation — the new rows
fail when the repair is removed, so they pin something.
⛔ No production file moved for this:
needsValueInput, the exported set andevery other export are byte-identical to
a835ae4cd1. The diff is the pin andthe changeset.
This section and the commit above it were written by a second Claude Code seat,
session
https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt— recorded hereas prose because the footer block below belongs to the seat that opened the PR
and a
PATCHrewrites footers.Contract review FAIL — the published doc-comment repaired
The clause-② review at
7daa352f98returned FAIL on one item, and it is not therepair itself: the repair falsified the doc-comment of the exported set it
deliberately left alone, and that comment SHIPS. Amended in
dfbee6340a, commenttext only — no membership change, no runtime line, the ruling untouched.
needsValueInputbelow is defined as the complement ofthis set, so the two cannot say different things", under a headline reading as
the set of spellings the builder leaves value-less. At the merge-base the first
was literally true; after this branch's fold the gate is the complement of that
set's FOLD-CLOSURE under
normalizeFilterOperator.commit both sentences were emitted verbatim into
dist/custom/filter-builder.d.ts(lines 151 and 157; declaration at 183).After it, both read 0 occurrences in the rebuilt file, paired with two lit
controls from that SAME emitted file: the
export declare constline (1 hit,now line 195) and the objectui#4744 citation (1 hit, now line 153). The three
corrected statements read 1 occurrence each, at lines 151, 157 and 162. All
counts are newline-tolerant occurrence counts, not line counts.
set's symbol and its docblock have 0 hits across all 208 emitted
declaration files, while the exported symbol lights twice in the same file.
The zero is therefore negative rather than void. The mechanism shows up again
in the line accounting below: the one added in-function comment line does not
reach the declarations at all.
PR already invokes elsewhere). The amended comment names the instrument, the
pin that walks the two published tables, and states no number.
emitted declaration file grows by exactly the 12 added doc-comment lines and
the declaration shifts by exactly 12. The pin runs 30/30 at
dfbee6340a,and the set of spellings whose verdict this branch changes is byte-identical
before and after the commit — re-derived over the whole published universe
(
VIEW_FILTER_OPERATORS,VIEW_FILTER_OPERATOR_ALIASESand the dropdown ids)from the installed
@objectstack/spec17.4.0 and from the BUILT export, withvalue-taking operators carried as unmoved controls so the divergence is a
reading.
type-checkexit 0;check:control-bytes,check:new-line-citations,check:comment-mask-corpus,check:changeset-claimsandcheck-changeset-presenceall exit 0.in the emitted
.d.ts, by construction: the repair IS a change to publishedtext. No declared type moves and the
export declare constline is unchanged,but the review's identical-rollup reading was taken at the previous head and
does not carry to this one.
objectui#9358citation — a PR, where its other citationsare cards — now cites the card. The declared level
patchis untouched, andthe review graded it correct.
Second contract review FAIL — the prescription repaired
The clause-② review at
dfbee6340areturned FAIL on the same item one level in:the first repair discharged two false published sentences and replaced them with
a third. Amended at this head, comment text only — no membership change, no
runtime line, the ruling untouched.
amended block shipped at emitted lines 162-164 of
dist/custom/filter-builder.d.ts, directly above the declaration, telling aconsumer holding a foreign spelling to fold it through
normalizeFilterOperatorand then ask this set — the exported set, unfolded — "exactly as that gate
does". Run literally over the whole published universe (
VIEW_FILTER_OPERATORS,the keys of
VIEW_FILTER_OPERATOR_ALIASESand the dropdown ids, from theinstalled
@objectstack/spec17.4.0, with the set read from the BUILT bundle),that procedure is value-less for
existsandnotExistsand for nothing else,where the gate is value-less for the whole fold-closure. It answers "takes a
value" for every canonical and every all-lowercase-alias spelling of the
null/empty operators — the exact population the sentence exists to serve — and
for this dropdown's four camelCase ids as well, so a consumer following it
landed strictly further from the gate than one still doing the pre-branch
raw
has(). The three-column table is on objectui#9302.than against the prose. "exactly as that gate does" — the gate folds BOTH
sides, and the set it consults is module-private and unreachable from the
published declarations by this PR's own clause-② measurement, so no consumer
reading the
.d.tscould do "exactly" that. And the block contradicted itselftwo sentences apart: it says the gate is the complement of this set's
FOLD-CLOSURE "not of this set itself", then instructs the consumer to ask this
set.
procedure a consumer can actually run from the PUBLISHED surface: fold this
set's own members through
normalizeFilterOperatoras well as thespelling, and ask the result. Executed over that same universe it agrees with
the gate on every spelling in it — with the two wrong procedures run through
the same harness as the controls, disagreeing on 12 and 8 rows respectively,
so the agreement is a reading and not a uniform answer. ⭐ The lesson this
round paid for, stated once: if the comment tells a consumer to DO
something, execute that thing before writing it down.
sentence reads 0 occurrences in the rebuilt
dist/custom/filter-builder.d.ts,paired with lit controls from that SAME emitted file: the
export declare constline, the objectui#4744 citation and the objectui#9302 citation, 1 hit each.
The replacement reads 1 hit, shipping at emitted lines 162-165. All are
newline-tolerant occurrence counts, not line counts.
stripped symmetrically, the code-only sha256 of
packages/components/src/custom/filter-builder.tsxis unchanged across thiscommit while the raw blob moves — and the merge-base's code-only digest
differs, the control that the instrument can see a real edit at all. Every
changed line in the diff is a comment line. The pin runs 30/30 at this
head;
type-checkexit 0.the pin that walks the two published tables and states no number.
that declares the population deliberately unlisted now names the instrument
instead. The declared level
patchis untouched, and the review graded itcorrect.
095830673a. The pin this PRadds cited
objectui#9358, a PR, in five prose spots, the same class as thechangeset citation already corrected, and it is inside this diff. The seat
took that one rather than leaving it: four of the five sites did not mean a
card at all — they mean "the first pass of this pin got this wrong", a fact
about this branch — and now say so in words; the fifth is the derived suite's
title, where the subject really is the pinned defect, so it cites the card.
objectui#9358now reads 0 occurrences in that file withobjectui#9302lit at 5 as the control. Prose only, mechanically: filtering the diff to
lines that are neither a comment gutter nor a line comment leaves exactly
one, the suite title. Pin still 30/30.
filter-builder-valueless-operators.test.tsx:11docblock still describes theexport as "the operator ids for which it renders no value input", which now
undercounts. It is not in this diff and it does not ship; it is carried on
objectui#9302 with a file and a line rather than widening a fourth-round PR.
Acceptance notes
Out of scope, noted, not filed here — carried so the reviewing seat sees it:
packages/plugin-list/src/ListView.tsx'sconvertFilterGroupToASTperformsthe SAME raw
has()on the same exported set and, unlikeapp-shell, doesnot compensate — the objectui#4744 failure mode one layer up, for the
canonical spelling. True when it was written; false now. objectui#9362 has
merged, and re-measured on
origin/mainatefc1c9c400,ListView.tsx:330-331derives its canonical set from this export with
.map(normalizeFilterOperator)and
:357asks it with the folded spelling. That is the same both-sides foldthis PR puts in the builder, and it is verbatim the procedure this head's
doc-comment now prescribes — which is also why that prescription is not
hypothetical. No successor is owed here, and the ruling on objectui#9302 still
refuses to reach that layer from this file by widening the export.
Not addressed here
Named as bare references on purpose, with no keyword in front of any of them, so
merging this PR leaves every one of them open:
objectui#7561, objectui#9306, objectui#7379, objectui#6939.
Generated by Claude Code
Generated by Claude Code