Skip to content

fix(plugin-grid,plugin-list): the grid and the list re-read when a harvested query input changes (objectui#10689) - #11280

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10689-harvest-inputs-in-fetch-deps
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10689-harvest-inputs-in-fetch-deps

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10689
Clause-②: no

ObjectGrid's load effect and ListView's fetch effect now re-read when an input their query reads changes, and only then. This is the objectui#10664 family rule ("every input the query reads is in the effect's dependencies"), in PR objectui#10688's shape: each input is keyed by content, as a string compared by value (AGENTS.md #10). What the query selects is unchanged; only when it re-reads moves.

The rows, re-derived on main (18d1a0abd3)

Row Input the query reads In the effect's dependencies at base? Change
grid (ObjectGrid load effect) conditionalFormatting (predicate-operand harvest) no predicateProjectionKey
grid rowActionDefs (harvest) no predicateProjectionKey
grid bulkActionDefs (harvest) no predicateProjectionKey
grid searchableFields ($searchFields) no searchFieldsKey
list (ListView fetch effect) conditionalFormatting (harvest) no predicateProjectionKey
list rowActionDefs (harvest) no predicateProjectionKey
list bulkActionDefs (harvest) no predicateProjectionKey
list searchableFields ($searchFields) yes (schema.searchableFields, by identity) none: already re-reads on main, pinned only

The keys

  • predicateProjectionKey, in both files: JSON.stringify(collectPredicateFieldRefs(listViewPredicates({ conditionalFormatting, rowActionDefs, bulkActionDefs }))). It is the same harvest over the same three view-level inputs that the effect runs. It keys on the operand NAMES, the shape groupingProjectionKey already takes for grouping in the grid, so a rule's style or an action's label costs no round trip. The object-level actions / userActions the effect adds come from the object definition, not the view, and stay outside the key.
  • searchFieldsKey, in the grid: the $searchFields the query sends. It is JSON.stringify(searchableFields) while a term goes out, and '' otherwise, because without a term the query does not send them.
  • No new helper. PR objectui#10688 added no named key helper: its keys are inline JSON.stringify(...) strings (sortKey, optionsFilterKey, tableSortKey) or a computed projection (lookupExpandKey). These two keys follow that shape.
  • The inputs are read through typed casts ({ rowActionDefs?: readonly unknown[] }, the way the grid already reads bulkActionDefs for its diagnostic), so the dependency lists hold simple names. The rowActionDefs read is the objectui#5091 NON-AUTHOR SURFACE exemption, as at the effects' own reads.

Red first, then ablation

Red first, at base. The two pin files, at their committed bytes, were run against the source at 18d1a0abd3:

  • grid: Tests 4 failed | 4 passed (8). The four defect rows were red (for the rule, $select stayed [['id','name']]), and the controls were green.
  • list: Tests 3 failed | 4 passed (7). The three defect rows were red. The list searchableFields pin was green, because that row was already keyed on main.

Ablation at head 1842dc0f83, through ablation-replace.mjs. The anchor hit went 1 to 0, and the restore was proven: blob equal to HEAD, git diff HEAD empty. No dist leg was needed, because the pins import the components relatively from src.

  • grid, with predicateProjectionKey, searchFieldsKey dropped from the dependencies: Tests 4 failed | 4 passed (8), the same four rows.
  • list, with predicateProjectionKey dropped: Tests 3 failed | 4 passed (7), the same three rows.

Pins (each counts find calls)

  • packages/plugin-grid/src/__tests__/ObjectGrid.harvestInputsFetchKey-10689.test.tsx:
    • A fresh mount with the rule reads once, with $select ['id','name','industry'].
    • A rule, a row def or a bulk def added to a mounted grid re-reads exactly once, with its operand.
    • searchableFields changed under a term re-reads once, with the new $searchFields.
    • Controls that add zero reads: an equal re-render (new arrays with the same content, all four inputs); a style-only rule change; a searchableFields change with no term.
  • packages/plugin-list/src/__tests__/ListView.harvestInputsFetchKey-10689.test.tsx: the same cases for the list's three inputs, plus the already-keyed searchableFields row.

Verification at head 1842dc0f83

  • Build: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-grid^...' build exited 0. Both closures are needed: plugin-list^... alone does not contain plugin-grid's plugin-detail, data-objectstack and sdui-parser.
  • Type-check: pnpm --filter @object-ui/plugin-grid type-check and pnpm --filter @object-ui/plugin-list type-check exited 0. tsc -p tsconfig.test.json --listFiles lists each new pin file.
  • Tests:
    • pnpm exec vitest run packages/plugin-list/: Test Files 110 passed (110), Tests 1208 passed (1208).
    • packages/plugin-grid/ ran in two chunks. Its 177 test files were split 88 + 89, and the union was checked against the full list. Results: 88 passed (88) / 766 passed (766), and 89 passed (89) / 855 passed (855).
  • Checks, each exit 0:
    • check:new-line-citations: VERDICT new-cross-file-line-citations: 0 new citation(s).
    • scripts/check-changeset-presence.mjs: 4 source files of 2 released packages, 2 changesets.
    • changeset:check, check:control-bytes, check:action-forward-parity, check:i18n-keys, check:spec-symbols, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit and check:icon-record-names.
  • Lint, narrowed to the four changed files (the tree-wide pnpm lint run is CI's):
    • None of the four is ignored by eslint.config.js: eslint --print-config resolves each one.
    • eslint --format json over them reported 4 files and 0 errors.
    • ObjectGrid.tsx and ListView.tsx carry the same warning counts as at base.
    • The narrowing excludes nothing. The config enables no type-aware linting (no parserOptions.project / projectService), and no rule under eslint-rules/ reads the filesystem, so this diff cannot move the verdict on any untouched file.

Acceptance notes

  • ListView names schema.searchableFields by identity in its fetch dependencies. A throwaway probe (deleted, never committed) measured it: an equal searchableFields in a new array, under a search term, re-reads (2 find). It is not edited here, for two reasons. The ruling says a row that already re-reads on main is pinned, not re-edited. And that effect's own comment records the objectui#4567 ruling that its by-identity dependencies stay, with stabilisation at the producer. No producer was measured rebuilding the array on every render. Carrier: none.
  • No README or guide changes: when a view re-reads is not documented behaviour for either block.

Changesets

  • .changeset/10689-grid-harvest-inputs-refetch.md (@object-ui/plugin-grid: patch)
  • .changeset/10689-list-harvest-inputs-refetch.md (@object-ui/plugin-list: patch)

Session: session_0122Knsowci76D2rBWReCzzZ, seat domain:ui#1, claim comment 5920079312.


Generated by Claude Code

…rvested query input changes (objectui#10689)

ObjectGrid's load effect and ListView's fetch effect harvest predicate
operands from conditionalFormatting, rowActionDefs and bulkActionDefs
into $select, and the grid sends searchableFields as $searchFields, but
none of those inputs was in the effect's dependencies. A rule added to a
mounted grid or list never had its operand fetched.

Each is now keyed by content: the harvested operand names
(predicateProjectionKey, the shape groupingProjectionKey already takes)
and, on the grid, the $searchFields the query sends (searchFieldsKey).
An equal re-render or a style-only rule change does not re-read.
ListView's searchableFields was already a dependency on main; it is
pinned, not re-edited.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…inputs through typed casts, not any (objectui#10689)

The key's inputs are hoisted into plain locals read through
`{ rowActionDefs?: readonly unknown[] }`-shaped casts, the way the grid
already reads `bulkActionDefs` for its diagnostic, so the dependency list
holds simple names. No behaviour change: the same three inputs, the same
content key. It removes the lint warnings the first commit added to both
files (no-explicit-any, and the complex-expression dependency warnings).

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 12 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 b55a34647 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/7070-no-invented-gantt-date-fields.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.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/7773-kanban-adapter-groupfield-write.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

.changeset/7780-object-kanban-record-source.md

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • packages/plugin-list/src/ListView.tsx generates the node as groupBy: laneField. objectDef loads asynchronously, so laneField is undefined on every load until it lands, and stays undefined whenever the object offers no stageField hint and none of status / stage / state / phase. The renderer serves that node; both published faces refused it. - content/docs/utilities/data-objectstack.mdx documents an object-kanban node that is exactly { type, dataSource }, with no groupBy. ⚠️ This one is weaker and is cited for what it is: that fragment is still refused after this change, at RECORD_SOURCE_REQUIRED, because dataSource is not a rung of the record-source ladder. It shows a lane-less board is a documented authoring; it is not a document this change admits.

.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/list-user-actions-collision-5398.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    The harvest now reads the object block only. Both userActions read sites in ListView.tsx carry a comment naming the collision, and __tests__/ListView.userActionsCollision.test.tsx pins each clause of it: the two shapes, a producer that manufactures the view one, the harvest's blindness to it, and the projection that must keep the object's operand with a toolbar block — or an empty block — present on the view.

.changeset/listview-comment-pair-4559.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Two comment corrections in ListView.tsx (objectui#4559, objectui#4966). No runtime behaviour changes and the emitted bundle is byte-identical; the published .d.ts does change, which is why this is a patch rather than an empty frontmatter.

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 e420df310 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1850 pending declaration(s) that publish a body (2462 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3584.0 KB 3607.4 KB
Main entry chunk (gzip) 150.0 KB 350 KB
Entry file index-DvDo85SI.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 570.09KB 136.40KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.86KB 63.53KB
fields (index.js) 261.11KB 66.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 41.23KB 11.53KB
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) 6.62KB 2.45KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.72KB 37.05KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 244.72KB 64.45KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.61KB 44.59KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.61KB 63.61KB
plugin-kanban (index.js) 48.62KB 15.21KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 2.52KB 1.31KB
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) 21.59KB 7.71KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1842dc0f8376f144cbb99f4be3a94ef9dc249839
Local-runs: none

Inputs read: card #10689 (body and all four comments: grade 5837859423, unlock 5912964658, claim 5920079312, dev report 5920883907); PR #11280 (body, its 6-file list, the net diff at merge-base 18d1a0abd3 — origin/main has 5 later commits and none touches ObjectGrid.tsx, ListView.tsx or core/src/utils/predicate-fields.ts, so the three-dot diff against main is the same +499 / -2 over 6 files); the head's 42 check-runs, re-read at 2026-09-30T22:39Z. Context only: card #10664 / PR #10688 (the family closure), card #4567 (the by-identity ruling the dev cites). Nothing built, run or re-run.

① Derived judgments

  1. Rows re-derived at the merge-base — right. The diff's removed dependency lines are the base lists. The grid's named groupingProjectionKey, querySearchTerm, invalidationNonce and the rest, and none of conditionalFormatting, rowActionDefs, bulkActionDefs, searchableFields. The list's named schema.searchableFields (by identity) and none of the three predicate carriers. That is the PR's table: seven rows unkeyed, one already keyed. The red-first counts the dev reports (grid 4 red of 8, list 3 red of 7) are the same seven rows.

  2. predicateProjectionKey keys every input the query reads, by content — right, and it hides nothing. Checked against the effect bodies at head. Both effects build $select through collectPredicateFieldRefs(listViewPredicates({...})) over schema.conditionalFormatting, (schema as any).rowActionDefs, (schema as any).bulkActionDefs, plus the object-level objectActions / userActions (grid :2471-2518, list :2615-2685). The key hands the SAME raw arrays from the same schema to the SAME two helpers. So every read listViewPredicates makes off those three inputs — a rule's condition, expression and native field; a def's visible, disabled, recordIdField, each defaultFromRow param key, each {field} token of target, and the undoable written keys — is inside the key, and the operand-name set the effect derives from the view cannot move without the key moving. What the key omits is exactly what the view does not supply: in the grid the object-level predicates come from getObjectSchema awaited INSIDE the effect (not a render input at all); in the list they come from objectDef, already named through objectDef?.fields / objectDefLoaded and outside this card. The post-harvest gates (isProjectableField, FLS) can make the key move without $select moving — one spurious read, never a missed one. Keying on the harvested names rather than each whole input is the family's accepted shape, not a narrowing of it: PR fix(plugin-map,components,plugin-dashboard,plugin-view): each data renderer's fetch keys on the query it issues, and the map reads once, expanded (objectui#10664) #10688's lookupExpandKey is "the expansion the columns produce, by content, so a relabelled column does not re-read", and the grid's own groupingProjectionKey keys grouping the same way. A style-only rule change costing no round trip is pinned in both files. Note, not a defect: collectPredicateFieldRefs returns first-seen order, so reordering rules re-reads once — the same order also flows into $select, so the query really differs.

  3. searchFieldsKey is the $searchFields the grid sends — right. Its condition (querySearchTerm.trim() non-empty and searchableFields.length above 0, else '') is the effect's own send condition at :2715-2720 (trimmedSearch = querySearchTerm.trim(), then under if (trimmedSearch) the $searchFields assignment guarded by schema.searchableFields having a length above 0). querySearchTerm was already a dependency, so the key covers exactly the transitions where the sent $searchFields changes. A plain string; no memo needed.

  4. Effect bodies unchanged; what the query selects is the same — confirmed. The zero-context diff shows the only changed lines inside either effect are comment lines and the dependency array. getSelectFields, addPredicateField, findParams and the search send are untouched. In the list the new prose sits ABOVE the directive warning block, so eslint-disable-next-line still governs the array; Lint is success.

  5. Typed casts, accept-set and public surface — unchanged, right. Commit 1842dc0f83 reads the three inputs through { rowActionDefs?: readonly unknown[] }-shaped casts; listViewPredicates accepts readonly unknown[] | null; Type Check is success. The rowActionDefs cast read is the objectui#5091 NON-AUTHOR SURFACE exemption already stated at each effect's own read. No new key is read, no prop type, schema, export or index.ts is in the diff. The behaviour change is one extra find when a rule or def adds a harvested operand, or (grid) when searchableFields changes under a term — the card's fix, counted by the pins.

  6. useMemo over the raw inputs by identity — right. The memo's inputs are identities, but its OUTPUT is a string; an equal-content new-identity input recomputes an equal string and the effect sees an equal dependency (AGENTS.md [WIP] Enhance every detail of the designer #10 holds where it matters, at the effect).

  7. ListView searchableFields: pinned, not re-edited — right under the unlock. The family rule the unlock restates ("every input the query reads is in the effect's dependencies") is met: schema.searchableFields IS named. The unlock says a row already fixed is pinned, not re-edited, and the card body itself scoped the list row to "the same three inputs". Re-keying it would also touch the effect's protected identity-dependency semantics: its own comment (:2822-2833) records Studio Data pillar grid issues a duplicate find() on every render — the columns array identity is in ListView's fetch dependency chain #4567's ruling — ListView's by-identity dependency stays, stabilisation at the producer — and a Studio Data pillar grid issues a duplicate find() on every render — the columns array identity is in ListView's fetch dependency chain #4567 pin drives a same-content new-identity re-read on purpose. The two renderers now differ in shape for this one input (grid content key, list identity); that follows from the rulings (the grid had NO dependency, so this card's shape applies) and is a note, not a defect.

  8. Pins — right, and they match the report. Grid file: 8 tests = 4 defect rows (rule, row def, bulk def, searchableFields under a term, each re-reading once with the new operand / $searchFields) + 4 controls (fresh mount once with ['id','name','industry']; equal re-render of all four inputs under a term adds 0; style-only 0; searchableFields with no term 0). List file: 7 = 3 defect rows + 4 (fresh mount, equal re-render, style-only, the already-keyed searchableFields row). Both import the component relatively from src, so no dist leg is owed. Red-first and the ablation (anchor 1 to 0, restore proven blob-equal to HEAD) are the dev's measurements and are consistent with the base dependency lines; not re-run here.

  9. Gate verdicts on the head (42 runs): 32 success, 3 skipped (the two coverage shapes and dependabot, not applicable), 0 failure, 7 not concluded. Concluded success includes Type Check, Lint, Build & E2E, Test (dist pins), Test (shard 3/8), Test (shard 6/8), Bundle Analysis, Line Citation Gate, Control Byte Scan and the five Changeset gates. Still in_progress at the re-read, named and treated as neither pass nor fail: Test (shard 1/8), Test (shard 2/8), Test (shard 4/8), Test (shard 5/8), Test (shard 7/8), Test (shard 8/8), Spec Main Shape Gate.

② Semver level

Two changesets, .changeset/10689-grid-harvest-inputs-refetch.md (@object-ui/plugin-grid: patch) and .changeset/10689-list-harvest-inputs-refetch.md (@object-ui/plugin-list: patch) — right. The diff publishes a re-read-timing fix inside two renderers' effects and nothing else: no export, type, prop or schema change; the pins and changesets are unpublished. Nothing is minor-shaped (no newly accepted input — all four keys were already read). Each changeset names the defect and the fix it ships without overstating (the grid's names searchableFields, the list's does not — matching the rows). Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report are all success.

Clause-②: no — on the claim and the PR, right: nothing changes what either block accepts.

③ Boundary flags

  • premise_still_valid: true — confirmed by the base dependency lines (①.1).
  • open_questions: [] — none owed.
  • Deviation 1 (order-gate build lead false; the union of both closures built): the dev's local build; the gate is Build & E2E, success. Answered.
  • Deviation 2 (no key helper in PR fix(plugin-map,components,plugin-dashboard,plugin-view): each data renderer's fetch keys on the query it issues, and the map reads once, expanded (objectui#10664) #10688): verified on that PR's diff — sortKey, optionsFilterKey, tableSortKey are inline JSON.stringify strings and lookupExpandKey is a .join(',') over a computed projection. Reusing the inline shape and adding no helper is right; a shared helper would have been a core export, a public-surface change the claim excludes. Answered.
  • Deviation 3 (keyed on the harvested names, not each whole input): the ①.2 judgment — right, and pinned. Answered.
  • Deviation 4 (two commits; the second converts (schema as any) to typed casts): verified, no behaviour change; Lint and Type Check success. Answered.
  • Deviation 5 (container restart mid-run; resumed at 2f385d4624, every gate re-run at head): no reviewable residue; the head's check-runs are the gate. Answered.
  • Deviation 6 (commit trailers): both commits carry the card reference in the subject, the session trailer naming session_0122Knsowci76D2rBWReCzzZ, and a model-free co-author trailer; no model-named line. Accepted as the dev cites objectui AGENTS.md; that file is outside this review's inputs and not re-read. Answered.
  • Deviation 7 (grid tests chunked 88 + 89 with the partition union verified): the dev's local run; the gate is the CI shards — 2 of 8 concluded success, 6 not concluded (①.9). Answered.
  • Out-of-scope finding (ListView names schema.searchableFields by identity; an equal array in a new identity under a term re-reads): an Acceptance note, not a gap in this card's letter (①.7). The measured direction is an over-read (a spurious round trip, never a stale projection), and with no producer measured rebuilding the array per render it has no reach — not fileable on the evidence, carrier none, agreed. If a producer is later measured, Studio Data pillar grid issues a duplicate find() on every render — the columns array identity is in ListView's fetch dependency chain #4567 puts the fix at that producer, and the dev's dedupe words cover the search. Not escalated.
  • PR Acceptance note (no README or guide change): right — when a view re-reads is not documented behaviour for either block; README Export Check success.
  • Not concluded at the re-read: the seven check-runs named in ①.9. Not waited on, not treated as pass or fail.

Implemented-by: claude/issue-10689-harvest-inputs-in-fetch-deps
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 22:47
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit be0ad00 Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10689-harvest-inputs-in-fetch-deps branch September 30, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants