Skip to content

fix(core,plugin-detail,plugin-form,plugin-grid): a render-time filter refusal names the operator instead of throwing (objectui#9050) - #10201

Merged
os-elon-musk merged 2 commits into
mainfrom
claude/issue-9050-filter-refusal-set-alignment
Sep 21, 2026
Merged

os-elon-musk merged 2 commits into
mainfrom
claude/issue-9050-filter-refusal-set-alignment

Conversation

@os-elon-musk

@os-elon-musk os-elon-musk commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #9050

Seat session, in prose because a footer does not survive an edit: session_01Xr7APep6jm1Zta3KUzPzZf. ⚠️ The five ObjectGrid.tsx coordinates this body cited were addresses in a tree this very diff moves; they are now bound to the tree each was read from, per the changeset-claim-re-read gate on this PR (⛔ correcting the number alone re-stales on the next insertion). Edited by the reviewing domain:ui seat from the dev's own re-taken readings; ⛔ no code changed and the head did not move.

Ruling-ref: comment 5749197961 — batch #196 item 2, letter C′, maintainer 「同意」 2026-09-20T10:21Z. Steps 1 and 2 in one PR, as the ruling directs. Rule 3 (no new save-time gate) is honoured: nothing here adds a validator.

Clause-②: no — re-derived from the diff at the foot of this body.


What changed

convertFiltersToAST refuses eleven authored shapes with a FilterOperatorError. toFilterNode delegates to it from four call sites that are not on the wire path, so three of them turned an authoring mistake into a render error with no classifyLoadError to translate it, and the fourth reported it as a network-ish load failure.

  • @object-ui/core gains toFilterNodeSafely. Its result is a UNION — { ok: true, node } or { ok: false, refusal } — so a refusal is not representable as undefined. That is the point, not a style choice: undefined means "no filter", i.e. every row, which is the silently unconstrained query objectui#9001 eliminated, and the reason the ruling refuses option A. TypeScript makes a caller narrow before it can build a query.
  • FilterOperatorError now carries operator and field as data, and filterRefusalSubject picks the token a diagnostic names. The eleven messages deliberately share no idiom (the file's own docblocks argue at length that they must not), so recovering the token by pattern would be a twelfth dialect that fails silently on the next rewording.
  • The three render-time readers keep the refusal as a value, send nothing, and render a state that names the operator.
  • ObjectGrid's defaultFilters leg keeps its existing throw — measured, it is already inside the load effect's own try — and now lands on the same render branch, so it is diagnosed instead of reported under "Error loading grid".
  • view.malformedFilter added to all ten locale packs and to the three affected createSafeTranslation tables.

⛔ Which shapes convert is byte-identical. This is a delivery change, not an acceptance one.


Step 1 — the measurement

Re-runnable. Driven through toFilterNodeSafely (the render-time entry, not convertFiltersToAST directly), against installed @objectstack/spec 17.4.0, which is the artifact this repo builds and ships against (@object-ui/core floor: ^17.3.0).

The eleven throws span two authoring vocabularies, so "does the protocol refuse the same input" has to be asked at the door that actually gates each one. ViewFilterRuleSchema — the schema the ruling names — gates throws 10 and 11; the object arm is FilterCondition's door. The two right-hand columns are the SLOT declarations, which decide whether a parse happens at all.

# throw arm converter protocol VALUE door ObjectGridProps.filter ObjectGridProps.defaultFilters ListViewSchema.filter RecordRelatedListProps.filter
1 L176 combinator value is not an ARRAY obj THROW ($and) FilterConditionSchema: REFUSE ACCEPT ACCEPT REFUSE REFUSE
2 L191 combinator member is not a condition OBJ obj THROW ($and) FilterConditionSchema: REFUSE ACCEPT ACCEPT REFUSE REFUSE
3 L389 $icontains empty / non-string comparand obj THROW ($icontains) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
4 L500 $not combinator obj THROW ($not) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
5 L539 bare ARRAY as equality comparand obj THROW (tags) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
6 L641 exotic object in comparand position obj THROW (created) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
7 L669 $regex operator obj THROW ($regex) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
8 L715 retired lowercase alias obj THROW ($startswith) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
9 L726 unknown operator obj THROW ($bogus) FilterConditionSchema: ACCEPT ACCEPT ACCEPT REFUSE REFUSE
10 L1040 ARRAY on a single-value view operator rule THROW (equals) ViewFilterRuleSchema: ACCEPT ACCEPT ACCEPT ACCEPT ACCEPT
11 L1171 icontains empty / non-string comparand rule THROW (icontains) ViewFilterRuleSchema: ACCEPT ACCEPT ACCEPT ACCEPT ACCEPT

Envelope controls — every REFUSE above is unreadable without these, and my first pass produced phantom refusals until I added them:

ObjectGridPropsSchema  envelope, no filter   : ACCEPT
ListViewSchema         envelope, no filter   : ACCEPT
ListViewSchema         envelope, valid rule  : ACCEPT
RecordRelatedListProps envelope, no filter   : ACCEPT
RecordRelatedListProps envelope, valid rule  : ACCEPT
ViewFilterRuleSchema   valid rule            : ACCEPT
FilterConditionSchema  valid condition       : ACCEPT

Negative controls — without these the doors could be inert and every ACCEPT would mean nothing:

ViewFilterRuleSchema   missing `field`       : REFUSE
ViewFilterRuleSchema   unknown operator      : REFUSE
ViewFilterRuleSchema   `in` with a SCALAR    : REFUSE
ViewFilterRuleSchema   `between` with 3      : REFUSE
FilterConditionSchema  a bare string         : REFUSE
ObjectGridPropsSchema  objectName: 42        : REFUSE

⭐ Those last four are what make row 10 a finding rather than a shrug. ViewFilterRuleSchema does couple value shape to operator — superRefine(checkViewFilterRuleValueShape) is present in the shipped artifact and it refuses in with a scalar and between with three members. It ends if (!isPair) return;, so every other operator is not checked, while the same schema's own value description says "every other operator takes a scalar". Row 10 is the converter enforcing what the protocol declares in prose and does not enforce at parse.

Rows 3, 7 and 11 are the same shape one layer over: @objectstack/spec/data's FILTER_TEXT_CASES declares these refusals, with expectRejection: true and code: 'INVALID_FILTER' — for $regex, for a dangling $options, and for an empty or non-string $icontains comparand. It is a backend conformance table, not a parse rule, so FilterConditionSchema.safeParse accepts all three.

The slot columns, and a correction worth stating plainly

On the installed 17.4.0 artifact this repo builds against:

ObjectGridPropsSchema:  objectName:     z.string().optional()            ← control, discriminates (42 REFUSED)
                        columns:        z.array(z.unknown()).optional()
                        filter:         z.unknown().optional()           ← unconstrained
                        defaultFilters: z.unknown().optional()           ← unconstrained
                        sort:           z.unknown().optional()

On the objectstack working tree (04d639c659, packages/spec/src/ui/component.zod.ts:2661, version string also 17.4.0, unreleased), filter has since been narrowed to z.array(ViewFilterRuleSchema, { error: ruleArrayFilterError(…) }).optional(), while defaultFilters is still z.unknown().optional(). The two builds also carry different describe text for that key, which is the cheapest way to tell them apart.

⇒ the ruling's rule 3 premise — "the protocol's parse IS the save-time gate" — splits by key, and by artifact. It does not fail wholesale:

  • ObjectGrid's schema.filter read — the render-time useMemo this PR rewrites (:1718 at e686f4d9a, :1730 at this head) — is pin lag, not a contract gap: narrowed upstream, absent from the floor this repo pins, so nothing gates it on the tree that ships today. Remedy is a release and a floor bump, not a spec change.
  • ObjectGrid's schema.defaultFilters read inside loadSchemaAndData (:2092 at e686f4d9a, :2123 at this head) — z.unknown().optional() in both trees. Nothing gates it and nothing is scheduled to. This one is the contract gap, and it is the strongest argument for why step 2's B is not redundant.
  • ListViewSchema.filter and RecordRelatedListProps.filter are z.array(ViewFilterRuleSchema) in the shipped artifact and already refuse all nine object-arm shapes structurally.

Everything in this section belongs to the protocol, not to this PR. The objectstack card carrying these readings is drafted and filed by the seat at ACCEPT (rule 1).


Boundary readings, re-taken on this branch

The card body says a render-time throw "takes the subtree down". Measured, that is not what happens on the schema path — and the difference decides what the pin must discriminate.

reading where
SchemaRenderer wraps every rendered node in a per-component SchemaErrorBoundary, auto-recovering on resetKey packages/react/src/SchemaRenderer.tsx — class at :624, the wrap at :2067
that boundary's fallback prints error.message, so the operator name does survive into it SchemaRenderer.tsx:661 — the heading above it is the generic Component "TYPE" failed to render
CONTROL — neither caller package has a boundary of its own plugin-grid 0 non-test ErrorBoundary hits, plugin-form 0
all three components are exported from their package entry, so a host can mount them with no SchemaRenderer above them plugin-detail/src/index.tsx:37, plugin-form/src/index.tsx:66, plugin-grid/src/index.tsx:23
an in-repo direct mount exists today apps/console/src/dev/DevRowActions.tsx:56 mounts ObjectGrid with zero SchemaRenderer in the file
⭐ the fourth call site is not render-time the toFilterNode(schema.defaultFilters) call inside loadSchemaAndData, within the try whose catch calls setError — so it already rendered a visible panel, under t('grid.errorLoading'). Coordinates, each bound to the tree it was read from: :2092, try :1809, catch :2258 at e686f4d9a; :2123, :1828, :2289 at this head 28cf61ab1

⇒ what B buys differs per site, and the PR says so rather than claiming one story for four:

  1. Containment where there is no boundary — a direct mount, or any host embedding these packages outside the console.
  2. The named diagnostic everywhere else, in place of "Component failed to render" (schema path) or "Error loading grid" (the defaultFilters leg).

A pin that only proved "the page did not blank" would have been green before this change on the schema path. Neither of these is.


Ablation

Mutated on disk by a refuse-on-zero-match writer, one leg at a time, anchor matched exactly once, verified in both directions, restored from HEAD under a trap armed with an absolute path, restore proven by blob hash and an empty git diff HEAD. vitest.config.mts:503 aliases @object-ui/core to packages/core/src, so a mutation there reaches the run with no rebuild.

leg mutation predicted observed
A — remove the catch toFilterNodeSafely's FilterOperatorError branch rethrows instead of returning { ok: false } all three caller pins RED, the eleven core rows RED, positive controls GREEN matched — 16 failed / 7 passed. The three positive controls stayed green, and so did the defaultFilters leg, which does not go through toFilterNodeSafely at all
B — remove the named operator filterRefusalSubject returns undefined, so the translated headline interpolates an empty subject the callers' toContain(operator) RED, their "filter is malformed" and "no query sent" GREEN ⚠️ first run did NOT match — only the eleven core rows reddened; all three caller files passed

⭐ Leg B's miss was a real defect in the pin, and it is the most useful thing this ablation produced. The banner renders the converter's own message under the headline, and that message repeats the operator token — so a toContain over the whole banner stayed green with the name removed from the translated headline. That is precisely the "names the operator is decoration" failure this work was told not to ship. The headline is now separately addressable (*-malformed-filter-subject) and the operator assertions read it; re-run, leg B reds 16 / passes 7, with "sends no query at all" and the positive controls among the survivors. The two halves are now separable, which is the property that makes the pin worth having.

Both legs restored cleanly: RESTORE PROVEN: blob 63228ed3fce7d894402887eca67de983616dd86e == HEAD blob, git diff HEAD empty.


Gates

Hand-derived from objectui's own package.json scripts and .github/workflows/ — dispatch-gates.mjs refuses for this repo (it derives families from the tree the process runs in, which is objectstack). Every exit code captured to a file and read back, ⛔ never through a pipe.

All readings taken at 28cf61ab1, this branch's head.

gate exit
vitest run packages/core/ packages/plugin-detail/ packages/plugin-form/ packages/plugin-grid/ packages/i18n/ 0 — Test Files 680 passed (680), Tests 8926 passed / 1 skipped (8927)
type-check on those five packages (tsc --noEmit + tsc -p tsconfig.test.json each) 0 — 0 occurrences of error TS
the four new pin files on their own 0 — Test Files 4 passed (4), Tests 23 passed (23)
pnpm exec eslint . (whole repo, 5230 files) 0 — 0 errors, 13449 warnings
pnpm check:control-bytes 0
pnpm check:test-path-roots 0
pnpm check:new-line-citations 0
pnpm check:changeset-claims 0
pnpm check:vi-mock-specifiers 0
pnpm check:vi-mock-inherit 0
pnpm check:i18n-keys 0
pnpm check:i18n-drift 0
pnpm check:i18n-dead-keys 0
node scripts/check-changeset-presence.mjs 0
node scripts/check-changeset-no-major.mjs 0
node scripts/check-governed-queue-guard.mjs --test over the six touched paths 0 — NOT GOVERNED, 6 paths checked against 5 governed surfaces

No narrowing is declared for eslint — the repo-wide run is the whole population (5230 files), not a subset, and it is green at this branch's head. For the record, type-aware linting is not enabled (eslint.config.js sets no project / projectService / parserOptions), so this diff cannot move the verdict on a file it does not touch.

⚠️ One reading that will mislead anyone who repeats it: eslint . --no-inline-config reports 97 errors in 80 files on this tree. That flag disables the repo's own eslint-disable-next-line comments, which are load-bearing here (react-hooks/static-components in particular), and it is not what CI runs. It is green both before and after this change with the flag off.

A changeset is present and declares minor for the five affected packages — ⛔ never major, per the fixed-group rule.


Clause-②: no, re-derived from the diff

The ruling's test: 「a render state on published components ⇒ expected no; any converter change that widens what converts is yes」.

  • The converter's accept set is unchanged. convertFiltersToAST's eleven refusal conditions are byte-identical; the only edit inside them is a second constructor argument carrying { operator, field }. The positive control in the core pin executes both directions — a valid object filter and a valid rule array still lower to exactly the nodes they lowered to before.
  • toFilterNodeSafely is a new entry point that wraps toFilterNode; it converts nothing toFilterNode did not already convert, and it narrows nothing either.
  • The render states are on published components and publish no authoring surface.
  • Nothing in packages/spec/** or in the objectstack repo is touched. The protocol-side deltas from step 1 are a card, not a diff.

⛔ The stop-and-report fork — "the alignment requires the converter to accept a shape it refuses today" — was not reached. No refusal was removed or relaxed.


Scope notes

  • Fence extension, declared. The dispatch fenced the converter, the three caller files and their tests, and separately required check:i18n-keys to pass with a real key rather than a hard-coded English string. A real key cannot exist in one file: all-locales-key-parity.test.ts requires every en key in all ten packs, and defaults-maps-mirror-en-pack requires the createSafeTranslation rows to be byte-identical to en. So this diff also touches packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts and packages/plugin-detail/src/useDetailTranslation.ts. None of those is on the held list (packages/i18n/src/provider.tsx is, and is untouched).
  • Held paths respected. packages/components/src/custom/filter-builder.tsx, packages/app-shell/src/hooks|console|layout, packages/i18n/src/provider.tsx, packages/plugin-tree/, packages/react/ — all read, none edited.
  • packages/react/src/SchemaRenderer.tsx was read for the boundary measurement and is not in the diff.

Acceptance notes

Noted, not filed — observations, out of the three filing classes:

  • ObjectGrid's sort slot is z.unknown().optional() on the installed artifact for the same reason filter is, and the narrowing to z.array(SortItemSchema) is in the objectstack tree unreleased. Same mechanism as the filter row above, different key; it rides the same floor bump, so it needs no card of its own. Carrier: the objectstack card this PR's step 1 produces.
  • FilterOperatorError.operator is absent on exactly two arms (bare-array and exotic comparand), where the author wrote no operator. filterRefusalSubject falls back to the field, so the sentence reads "the tags condition cannot be applied", which is accurate but is a field where the copy says condition. Carrier: none — it is a wording judgement on new copy, not a defect.

The dependency closure for the packages above was built first under the shared verify lock, so no TS2307 / TS6305 prerequisite failure is hiding in these readings.


Generated by Claude Code

… refusal names the operator instead of throwing (objectui#9050)

`toFilterNode` delegates to `convertFiltersToAST`, which refuses eleven authored
shapes with a `FilterOperatorError`. Four call sites reach it off the wire path:
three from a render-time `useMemo` (`RelatedList`, `LineItemsPanel`,
`ObjectGrid.schema.filter`), where a throw is a render error with no
`classifyLoadError` above it, and one inside `ObjectGrid`'s load effect
(`defaultFilters`), which already caught the refusal but reported it as a
generic load failure.

- `@object-ui/core` gains `toFilterNodeSafely`, returning a UNION so a refusal
  cannot be read as `undefined` — which means "no filter", i.e. every row, the
  silently unconstrained query objectui#9001 closed.
- `FilterOperatorError` carries `operator` / `field` as data; the eleven
  messages share no idiom, so scraping a token out of them would be a twelfth
  dialect.
- The three render-time readers send nothing and render a state naming the
  operator; `ObjectGrid`'s load-path refusal lands on the same branch.
- `view.malformedFilter` added to all ten locale packs and the three affected
  `createSafeTranslation` tables.

Ruling-ref: objectui#9050 comment 5749197961 (batch #196 item 2, letter C′).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xr7APep6jm1Zta3KUzPzZf
… reads the HEADLINE, not the whole banner (objectui#9050)

Ablation leg B — `filterRefusalSubject` returning `undefined` — was predicted to
red the three caller pins and did not: the technical line under the headline is
the converter's own message and repeats the operator token, so a whole-banner
`toContain` stayed green with the name removed from the translated headline.
That is the decoration this card was told not to ship.

The headline is now separately addressable and the operator assertions read it.

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 5 address(es) in this pull request's own prose name a tree it replaced

Each was read from a tree this change itself moves, so a reader who follows it lands somewhere else. ⛔ Nothing here blocks and nothing here says the sentence is false — the question asked is arithmetic: does this diff move the line that number points at?

  • in this body, ObjectGrid.tsx:1718 — this change rewrites packages/plugin-grid/src/ObjectGrid.tsx:1718

    • ObjectGrid.tsx:1718 (schema.filter) — narrowed upstream, but that narrowing has not reached the floor this repo pins, so on the tree that ships today nothing gates it.
  • in this body, :1809 — this change moves packages/plugin-grid/src/ObjectGrid.tsx:1809 to :1828

    | ⭐ the fourth call site is not render-time | ObjectGrid.tsx:2092 sits inside loadSchemaAndData, within the try opened at :1809 whose catch at :2258 calls setError — so it already rendered a visible panel, under t('grid.errorLoading') |

  • in this body, ObjectGrid.tsx:2092 — this change moves packages/plugin-grid/src/ObjectGrid.tsx:2092 to :2123

    • ObjectGrid.tsx:2092 (schema.defaultFilters) — z.unknown().optional() in both.
  • in this body, ObjectGrid.tsx:2092 — this change moves packages/plugin-grid/src/ObjectGrid.tsx:2092 to :2123

    | ⭐ the fourth call site is not render-time | ObjectGrid.tsx:2092 sits inside loadSchemaAndData, within the try opened at :1809 whose catch at :2258 calls setError — so it already rendered a visible panel, under t('grid.errorLoading') |

  • in this body, :2258 — this change moves packages/plugin-grid/src/ObjectGrid.tsx:2258 to :2289

    | ⭐ the fourth call site is not render-time | ObjectGrid.tsx:2092 sits inside loadSchemaAndData, within the try opened at :1809 whose catch at :2258 calls setError — so it already rendered a visible panel, under t('grid.errorLoading') |

⛔ The repair is not to correct the number. Changing :246 to :274 is true today and born false again on the next insertion — objectui#9509 states that before anything else. Bind the number to the tree it was read from (`:246` at `b8a006883d`, `:274` at this head), which cannot re-stale because each number names its own tree; or state a rule instead of a coordinate, the way objectui#9495 replaced a file count with "every file in git diff --name-only against the merge base".

⚠️ 7 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/4730-retire-dead-locale-key-batch.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • Superseded twin vocabularies. cellRender.* and rowAction.* duplicated a grid.* vocabulary that won. RowActionMenu.tsx is fully i18n-wired and reads grid.openMenu / grid.edit / grid.delete; ObjectGrid.tsx reads grid.empty / grid.yes / grid.no / grid.systemFields. The twins had no reader on either side. - Labels that outlived their control. calendar.agenda labelled a view mode objectui#5740 retired from CalendarViewMode (now 'month' | 'week' | 'day'). home.quickActions.createApp*, layout.systemNav.createApp, actionDialog.defaultActionTitle / .ok and grid.bulk.selectPlaceholder sit in namespaces whose consumers are live and wired but demonstrably read other siblings. - Surfaces that left the product. map.* is the strongest form: @object-ui/plugin-map declares no @object-ui/i18n dependency and contains no t() call at all, so it cannot consume a locale string. home.stats.* and recordDetail.viewersTooltip name surfaces nothing renders.

.changeset/6597-retire-fieldmeta-referenceto.md

  • names plugin-grid/src/ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    No authoring story survived the search either. ObjectGrid's own relational-meta pass-through (applyRelationalMeta, plugin-grid/src/ObjectGrid.tsx) copies reference_to / reference / display_field / etc. from the SCHEMA field def only, at all three of its call sites — never from an authored column override. No doc, example, or fixture in this repo shows a table column pinning a lookup's target away from what its schema field already says. Under the maintainer's standing startup-stage rule (2026-08-27: deprecated/alias spellings retire immediately, no transition windows), no measured demand selects withdraw.

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/7762-object-grid-export-options-bare-array-refusal.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    What was measured, on this branch's base. The mirror declared NO exportOptions member at all, and BaseSchema is .passthrough(), so ObjectGridSchema.safeParse({ type: 'object-grid', objectName: 'accounts', exportOptions: ['csv', 'xlsx'] }) returned success: true with the array back VERBATIM — as did { formats: ['csv', 'pdf'], compression: 'gzip' }. Nothing on the render path parses, and ObjectGrid.tsx reads schema.exportOptions?.formats and only that, so the authored array then lost SILENTLY to the ['csv', 'json'] default: the useEffect that warns about dropped formats reads .formats too and returns early when it is absent, while !!schema.exportOptions kept the export button on screen. An author declared ['csv', 'xlsx'] and got csv/json with no error, no warning and no console line. The two authoring faces disagreed in the direction opposite to objectui#6956's: the TypeScript interface already declared the object form only, so TS refused what zod admitted.

.changeset/9722-bulk-executor-datasource-face.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • ObjectGrid.tsx → RecordDetailPanel: removing it type-checks GREEN. It was paying for nothing — both sides already declare the same DataSource | undefined from @object-ui/types. Removed outright. - ObjectGrid.tsx → BulkActionDialog: removing it reddens, and it was paying for TWO things at once — the optional-vs-required arm (the grid declares dataSource?: DataSource, the dialog demands one) AND the bulk-door contravariance. Only the first still needs erasing, so it is now a non-null assertion: today's runtime behaviour is preserved exactly, and any future drift of the face reddens at that site instead of passing silently.

.changeset/9853-grid-non-positive-page-size.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    Refuse a non-positive pageSize at all three of ObjectGrid.tsx's read points, instead of giving two different answers for one authored value (objectui#9853).

.changeset/plugin-detail-8937-parent-scope-residue.md

  • names RelatedList.tsx → packages/plugin-detail/src/RelatedList.tsx — edited by this change

    • packages/plugin-detail/README.md (it is in files[], so it ships). It said the node's filter is AND-combined with { [relationshipField]: parentId }, full stop. Since objectui#7299 the parent condition is compiled to match the relationship field's arity, so a multi-valued relationship gets { [relationshipField]: { $contains: parentId } } instead. The paragraph now states both spellings and names the arbiter (@objectstack/spec/data's isMultiValueField). - The claim that the SQL driver decides arity on that same predicate. It does not: driver-sql gates the equality family on its own storage question, which reads multiple as truthy on ANY type. The two rules therefore disagree for a type outside MULTI_CAPABLE_TYPES carrying multiple: true. objectui#9184 moved the arity compiler into @object-ui/core's parent-scope seam and carried the claim with it, so the correction is recorded there — the seam now states the driver's measured rule, records the divergence as a divergence, and points at the upstream card that owns which of the two rules is right (objectstack#17469). RelatedList.tsx's pointer comment and the objectui#7299 test header carried the same sentence and are corrected to match.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 663680162 (merge-base with origin/main): 19 file(s) changed outside .changeset/, read against 1261 pending declaration(s) that publish a body (1828 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3035.4 KB 3104.5 KB
Main entry chunk (gzip) 147.5 KB 350 KB
Entry file index-Cnq8Uawj.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
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) 546.84KB 130.95KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 252.81KB 63.78KB
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) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.02KB 11.00KB
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) 5.52KB 2.10KB
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) 51.44KB 14.62KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 257.23KB 66.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 143.93KB 36.81KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 214.23KB 58.45KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.53KB 27.99KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.49KB 11.97KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
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) 106.60KB 35.16KB
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 (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
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) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
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) 17.15KB 6.32KB
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants