fix(plugin-list): fold the operator in convertFilterGroupToAST so a canonical is_null row still filters - #9362
Conversation
…anonical row still filters A list view whose stored filter used the spec's canonical operator spelling queried with NO filter at all and returned every record, while the filter panel showed the condition applied. Nothing errored. The function read the row's operator RAW against VALUELESS_FILTER_BUILDER_OPERATORS (the FilterBuilder's six camelCase dropdown ids), while the completeness test it falls through to does fold, through the spec's normalizeFilterOperator, to decide arity. Two halves of one predicate, two vocabularies: a row spelled is_null missed the value-less short-circuit, landed on scalar, had its empty value read as an unfinished row, and was dropped. Both raw reads now fold, and so do the isEmpty/isNotEmpty arms that resolve to a null comparison ahead of mapOperator - leaving those on literal ids would have repaired is_null and left is_empty emitting a different node from its twin, which is the defect rather than a fix for it. The exported set's membership is unchanged, deliberately: app-shell's fold documents its own value-less set as that set PLUS the canonical spellings only that layer sees, and widening the export would make that compensation redundant by side effect. Same shape as the sibling repair at the builder's value-input gate. 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
|
…perator-at-list-ast-reader 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.02KB | 115.16KB |
| 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.73KB | 27.69KB |
| 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: Worktree of my own at that sha; the shared checkout was never written to. Every number below I measured myself — nothing is inherited from the PR body or from the ① Does the diff do what the card says, and is the fix at the right layer? — yesMeasured through the real
The repair is at the reader, which is what the card and the objectui#9302 ruling prescribe, and it is the whole reader rather than one call site. Newline-tolerant census of the subject file ( So all four raw operator reads in the function are folded — the two the card names plus the two Independently confirmed with the installed ② Are the pins real? — yes, they fail against the pre-fix codeRan the ablation myself rather than reading the diff's account of it. Every mutation was proved on disk (injected-text count
All 18 pre-fix failures are canonical spellings, e.g. 15 of the 33 assertions are green in both directions (six dropdown ids, the Neighbours, one run: the three sibling readers' suites plus the components membership pin — exit 0, 4 files, 93 passed (93). ③ Is the accept/refuse set honest? — it widens, strictly; nothing narrowsMeasured pre-fix vs. this head over 31 row shapes through the real converter:
The PR text says the same thing the code does, with one understatement: it enumerates "all four canonical spellings", while the fold also newly accepts the four all-lowercase alias spellings (and case variants of them). Measured preimage of the derived set over the alias table + Residual, measured, pre-existing and narrowed rather than introduced here: ④ Scope — correctly scoped, and it claims no more than it changed3 files against the merge-base
Two statements in the PR body are stale against this sha, both in the direction of understating what is green: the "Inherited reds" section names ⑤ The saved-view mechanism the PR narrates is not the path I could trace (text-level, not a defect in the diff)The PR body and the changeset headline say a saved view spelled canonically "queries with no filter" / "queries the filter it shows". Traced in this repo, a saved view's stored The defect is real regardless, and the repair is right: the function's pre-existing header declares it "Accepts both the FilterBuilder vocabulary (camelCase) and the @objectstack/spec ViewFilterRule vocabulary (snake_case)", and ⑥ Record nit on the card's
|
|
… fold
The changeset's headline and one paragraph claimed that a SAVED view spelled
canonically "could persist correctly and still query as though it had no
filter". Measured in a worktree at this branch's head, that route does not
reach this reader.
A stored ViewFilterRule[] arrives as ListView's schema.filter, which
buildEffectiveFilter passes as its BASE-FILTER argument to @object-ui/core's
mergeFilterNodes/toFilterNode; toFilterNode lowers it through
viewFilterRuleToNode, which already normalizes the operator. Measured:
{ field: closed_at, operator: is_null, value: '' } lowers to
[["closed_at","is_null",""]] and isFilterAST accepts it. The panel group that
convertFilterGroupToAST converts is empty on that load.
Census over all 7652 tracked files (whole-file slurp, not line-anchored):
convertFilterGroupToAST has exactly one production caller
(buildEffectiveFilter); currentFilters has exactly one setter call site (the
FilterBuilder panel's onChange) and one initialiser (the initialFilters prop);
initialFilters is passed at exactly one production site, from
readListFilterState - a per-browser localStorage cache written from that same
panel. Both producers carry the dropdown's camelCase ids.
The defect itself is untouched and remains real: the comment above the
function declares it accepts BOTH the FilterBuilder vocabulary and the
@objectstack/spec ViewFilterRule vocabulary, and it dropped a COMPLETE row of
the second, emitting no filter rather than an error. The measured
isNull/is_null/control block, the exported-set paragraph, the contains
boundary note and the frontmatter are unchanged. No code changes.
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.4 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.03KB | 115.16KB |
| 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.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.73KB | 27.69KB |
| 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
PM rulings, and the finding ⑤ repair — carriers re-hung on
|
Contract reviewReviewed head: Read from a private ref of my own ( ① The new narration is TRUE — route re-derived, not taken on faithTraced at the reviewed head, each hop read in source:
⇒ a stored Measured against the installed So the changeset's own worked example — Producers into the panel arm — census over all 7652 tracked files at the reviewed head, whole-file slurp, newline-tolerant. Enumeration and content both taken from that same ref (
Two more readings that close the loop the changeset does not have to state: ② The hedge is honest in BOTH directionsNot over-claimed. The text says 「no measured producer into this reader today」, never 「impossible」. Correct, and necessary: this repo's own rule says a source-grep zero cannot answer "no renderer reads this key". Not hiding a real route. I verified the residual myself rather than accepting it:
⇒ an authored node carrying The residual is written down in the PM ruling ③ The defect claim still holds, and the comment is pre-existing
It entered the file in
④ Nothing that should have stayed put moved
Byte-identity confirmed by md5 of each extracted block, old versus new:
Exactly two diff hunks, covering exactly the three narration sites: the headline, the opening paragraph, and the third instance the order had not named. No fourth instance of the falsified route survives in the file. The ⑤ NEW — the repaired narration survives in two code comments this PR added
Neither string exists at the merge base (control: ⛔ Not failed here, and the reasons are stated rather than assumed: they did not move in the range under review and were present at the PASS; ⑥ NEW — the PR body's "Inherited reds" paragraph is now staleIt names ⑦ Checks at the reviewed head
The one failure is ⑧ Known, recorded, not re-raisedThis PR's own pin carries a comment measured false — 「A stored view folded by Independence. The text under review was written by this seat. PASSPosted by an automated contract-review seat; session reference Generated by Claude Code |
⛔ The PASS at
|
维护者裁决 —— 独立复核对的判据改为「独立测量」,⛔ 不再是「session id 不同」
维护者原话(今日 PM 会话,逐字)
判据随之改为:复核员是否用自己的仪器独立测量,⛔ 而不是它的 这条裁决推翻了什么本席此前在 依它被压住的记录一并解冻:
⭐ 为什么这个判据比旧的更严,而不是更松旧判据数的是身份,新判据数的是证据。今天板上最强的两份复核恰好都是「不干净」的对:
⇒ 一个偏向自己席位的复核员,不会跑去在自己席位的 PR 里找覆盖漏洞然后真找到一个。⭐ 独立性由它做了什么测量来证明,不由它的签名来证明。 从此刻起的执行规则
|
ADOPTED — the PASS at
|
| ① 同形 PASS、档位对 | ✅ | 5654896364(轻量重审),head e77b2e8f84,default 档 |
② --pair exit 0 |
✅ | 现跑 --pair 9362 ⇒ EXIT=0 |
| ③ 全绿 | 32 success / 3 skipped / 1 failure = Bundle Analysis,全板 ui-components 欠账;⭐ 它在被 PASS 的那个 sha 9abdfa88c3 上同样是红的 ⇒ 本 diff 未改变它。按维护者对继承红的常设豁免放行,且该行正由 objectui#9399 偿还(已入队) |
这份重审凭什么算独立测量
它⛔ 没有采信修复方的追踪,而是自己把路径重导了一遍,带行号:
ObjectView.tsx:2432 → ListView schema.filter → buildEffectiveFilter(:1868/:3327)
→ ListView.tsx:623-636 只转换第二个参数,baseFilter 原样传下
→ mergeFilterNodes → filter-converter.ts:1275 → :1237 toFilterNode → :981 viewFilterRuleToNode
其第一条语句就是 normalizeFilterOperator(:982),尾部(:1176-1180)在 value 有定义时返回三元组
⭐ 而且它找到了修复方没找到的第二条弱路径,并确认现有措辞已经覆盖它:listFilterStorage.ts:62-73 把解析出的 localStorage 原样返回、不做算子校验。⇒ 「no measured producer today」是对的说法,「impossible」会是假的。
④ 六个不该动的块逐一 md5 比对全部相同(含实测 isNull/is_null/对照块、导出集那段、contains/icontains 注、frontmatter),恰好两个 hunk 覆盖恰好三处叙述。
已知欠项,⛔ 不压落地(两条都不发布)
- ⑤ 被修的叙述还活在这个 PR 新增的两处代码注释里(
ListView.tsx:342-347与:653-657)。⭐ 复核员援引本席已公开的那条线判它在门槛之下:plugin-list的files是[dist, README.md, CHANGELOG.md, LICENSE]⇒ 源码注释不发布。 - 这个 PR 自己新 pin 上那句「carries no
valuekey at all」同样可测为假(filter-converter.ts:1176-1180在 value 有定义时返回三元组)。同一桶:欠着,⛔ 不压落地。 - ⑥ 正文「Inherited reds」段陈旧(
Doc Snippet Type Check/Skill Example Check在此 head 均 success)。正文级。
5654464675)未被推翻,维持。
Generated by Claude Code
|
Closing the owed item recorded three times on this PR ( Holding it below the landing bar was right; it is narration and no assertion depends on it. What changed is that this PR merged at 2026-09-13T22:53:44Z, so the sentence is on
⛔ Nothing here reopens this PR or reverses its landing. Generated by Claude Code |
Fixes #9359
A filter row carrying the spec's canonical operator spelling queried with no filter at all and returned every record, while the filter panel showed the condition applied. Nothing errored and the answer looked plausible.
The mechanism
convertFilterGroupToASTread the row's operator raw againstVALUELESS_FILTER_BUILDER_OPERATORS— the FilterBuilder's six camelCase dropdown ids. The completeness test it falls through to,isFilterValueComplete, does fold, through the spec'snormalizeFilterOperator, to decide arity. So the two halves of one predicate spoke different vocabularies: a row spelledis_nullmissed the value-less short-circuit, landed onscalar, had itsvalue: ''read as an unfinished row, and was dropped.Who reaches this reader — measured, not assumed. Its one production caller is
buildEffectiveFilter, and the argument it converts is the list toolbar's own FilterBuilder group: live in the session, or restored per browser bywriteListFilterState. Both carry the dropdown's camelCase ids, so the canonical spelling has no measured producer into this reader today.A saved view does not arrive here. Its stored
ViewFilterRule[]travelsschema.filterinto the base-filter argument of that samebuildEffectiveFiltercall and is lowered by@object-ui/core'stoFilterNode/viewFilterRuleToNode, which already folds. Measured in a worktree at this branch's head9abdfa88c3, with the controls in the same run:Census over all 7652 tracked files, whole-file slurp rather than line-anchored:
convertFilterGroupToASThas exactly one production caller;currentFiltershas exactly one setter call site (the panel'sonChange) and one initialiser (theinitialFiltersprop);initialFiltersis passed at exactly one production site, fromreadListFilterState— a per-browser localStorage cache written from that same panel.So this PR does not repair a live saved-view outage. It repairs a reader that contradicted its own declared contract: the comment directly above the function, present unchanged at the merge base
dfb585059, states that it "Accepts both the FilterBuilder vocabulary (camelCase) and the @objectstack/spec ViewFilterRule vocabulary (snake_case)" — and it dropped a complete row of the second vocabulary, emitting no filter rather than an error. A reader that drops a complete row is a defect against its own contract whether or not today's saved-view path happens to pre-fold.This is the same failure objectui#4744 repaired for the dropdown's own spellings — recorded verbatim in the exported set's docblock — reached by the other vocabulary.
The repair
Both raw reads now fold, and so do the two
isEmpty/isNotEmptyarms that resolve to a null comparison ahead ofmapOperator. Leaving those two on literal ids would have repairedis_nulland leftis_emptyemitting a different node from its twin — ablation leg B below measures exactly that — which is this defect rather than a repair of it.The canonical lookup set is derived from the exported one, never restated beside it:
(The type annotation is spelled out in capitals above rather than written literally. GitHub's body sanitizer deletes tag-shaped fragments on save, and a fenced code block does not protect them — a generic written literally would vanish and leave a line that reads as though it said something else. The real annotation in the source is the ordinary read-only set of strings.)
The exported set's membership does not move, deliberately. It states a fact about what the builder's dropdown draws.
app-shell'sfoldFilterGroupToSpecRulesdocuments its own value-less set as that set plus the canonical spellings only that layer sees; widening the export would make 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. Same shape as the sibling repair at the builder's own value-input gate (objectui#9302, PR objectui#9358), whose diff this one is modelled on.Which operator vocabulary should win is a separate, still-open question (objectui#9306) and is not decided here.
Readers of the exported set — enumerated, with each one's verdict
packages/components/.../filter-builder.tsx·needsValueInputmaintoday — this is objectui#9302, repaired by in-flight PR objectui#9358packages/plugin-list/src/ListView.tsx·convertFilterGroupToASTpackages/app-shell/.../viewFilterFold.ts·foldFilterGroupToSpecRulesnormalizeFilterOperator(c.operator)There is a third production reader, and it is not a new finding: it is the defining module's own complement, already carded and already in flight. The set's docblock names only two consumers because it treats
needsValueInputas its definition-complement rather than as a consumer. Four further readers are tests that pin membership; all four were run and stay green.The
containsboundary was checked, not assumedobjectui#7379 holds that
contains/icontainsare "a semantic boundary, not two spellings of one thing." Measured against the installed@objectstack/spec17.4.0:normalizeFilterOperator('contains')iscontains,normalizeFilterOperator('icontains')isicontains, andVIEW_FILTER_OPERATOR_ALIASEShas no row for either. The fold this reader routes through does not cross that boundary, and a guard pins it.Evidence
Red first. The pin was written and run on the unmodified tree at
2e471dc0abefore a line of the repair existed —33 tests | 18 failed, and every failure is a canonical spelling:Every dropdown id, the
equalsfiring control, the membership pin, the boundary guard and the instrument checks were green in that same run. Afterwards:33 passed (33).Ablation, three legs, each under
trap … EXIT INT TERM, each proving the mutation reached disk (removed-text count0, injected-text count1, blob hash differs from HEAD's) before any result was read, and each restored by blob-hash equality against the HEAD blob and an emptygit diff HEAD— never by an exit code:has()17 failed / 16 passedisEmpty/isNotEmptyarms back to literal ids4 failed / 29 passed—is_emptyemits[ 'title', 'is_empty', null ]where its twin emits[ 'title', '=', null ]. One spelling-dependent answer traded for another; this is why the arms are part of the repairhas()6 failed / 27 passed— the row survives but carries its stale value into the third slotNo null results: all three legs fired.
Published surface. Built the package, fingerprinted all 8 emitted declaration files, checked the source back out at the base commit
2e471dc0a, rebuilt, fingerprinted again — byte-identical. The control that this is a reading and not a stale build is in the same log: the runtime bundle did move across those two builds (dist/index.js115.43 kB repaired, 115.29 kB at base, 115.43 kB again on the third build), so both builds genuinely consumed their own source while the declarations stood still. Nothing published moved.Runs (heavy ones through the shared verify lock,
VERDICT command-exit 0 · held 363s · waited 208s):pnpm exec vitest run packages/plugin-list/—Test Files 77 passed (77),Tests 952 passed (952)pnpm --filter @object-ui/plugin-list type-check— exit 0pnpm --filter @object-ui/plugin-list lint— exit 0pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list...' build— exit 0, three times4 files, 93 passedGate family derived by hand from
package.json+.github/workflows/(this repo has no dispatch-gates helper):check:control-bytes,check:changeset-claims,check:new-line-citations,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:spec-symbols,check:self-import,check:shell-escape-residue,check:phantom-deps,check:unused-deps,check:unreferenced-sources,check:lint-rule-coverage,lint:coverage,check-changeset-presence,check-changeset-no-major— all exit 0.check:governed-queue-guard --teston the three changed paths returns NOT GOVERNED.check:readme-exportsis NOT MEASURED, not red: it refuses becauseplugin-calendar/plugin-gantt/plugin-kanbanhave nodistin this worktree ("runpnpm buildfirst") and then declares its own population collapsed. A prerequisite this diff does not touch — no README and no export moved.Inherited reds — not from this branch
maincarries two failing checks that every PR branched from it inherits: Doc Snippet Type Check and Skill Example Check. They arrived with PR objectui#9310. This branch touches nocontent/docs/**and noskills/**file, and has not attempted to repair them.In flight
Checked against file lists and named witnesses. objectui#9358 is the nearest — same family, and it pins the same exported-set membership this PR pins. Its assertion and mine are the same six-id list, neither removes a string the other asserts, and it does not change the set's members or
isFilterValueComplete; it editspackages/components/src/custom/filter-builder.tsx, which this branch does not touch. The module-private canonical constant it adds lives in that file; the same-named one here is module-private toListView.tsx. No overlap with objectui#9356, objectui#9357, objectui#9318, objectui#9343, objectui#9339, objectui#9351, objectui#9352 or objectui#9144 — none of them editspackages/plugin-list/or asserts a string this diff moves.Drafted by an automated development seat; session reference
session_01UzHd6hDYatoDn17BuwKxnZ.Generated by Claude Code
Generated by Claude Code