Skip to content

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

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-10664-fetch-deps-match-query
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/issue-10664-fetch-deps-match-query

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10664
Clause-②: no

Each data renderer's fetch effect now keys on the inputs of the query it issues. The map's query waits for a settled definition, so a mount reads once and that read is expanded. The repeater and four other census rows re-read when a query input they send changes. None of them re-reads when an equal value arrives in a new object. Folds objectui#10665 (the repeater row), per the triage.

Scope, per the rulings: fetch gating and dependency lists only. No view's error lifecycle is touched; that is objectui#10663, dispatched in parallel. Every new key is a string compared by value (AGENTS.md #10).

What changed, per renderer

Renderer (package) Mismatch on the base Change
object-map / ObjectMap (plugin-map) Over-key. The definition sat in a local useState fed by its own effect and was listed in the fetch effect's dependencies. Measured: 2 find per mount, first without $expand; switching the bound object sent the new object's query with the previous object's expansion ([['owner'], ['manager']] for depot). The definition comes from useSettledSchema, keyed on recordSourceObjectName. The object branch waits on ready (the objectui#7895 / objectui#7903 gate) and holds the loading placeholder while it waits. Host rows and inline value sets are not held.
element:repeater / RepeaterRenderer (components) Under-key. properties.sort goes onto $orderby; the dependency list had no sort. sortKey, by content, the way filterKey keys the filter.
element:record_picker (components) Under-key, same shape: sort (flat, or the dataSource binding's) goes onto $orderby, not keyed. sortKey, by content.
object-data-table / ObjectDataTable (plugin-dashboard) Over-key, the map's shape. Measured: expand sets [null, ['account', 'owner_dept']] per mount. Under-key: the expansion reads schema.columns, which was not keyed. useSettledSchema gate on the fetching branch. lookupExpandKey (the expansion the columns produce, by content), so a relabelled column does not re-read.
Dashboard filter bar options (plugin-dashboard) Under-key. optionsFrom.filter goes onto both option reads (runtimeFilter, $filter), not keyed. optionsFilterKey, by content.
object-view / ObjectView non-grid read (plugin-view) Under-key. The read falls back to schema.table.sort for $orderby; the named-view and active-view sort sources are keyed, this one was not. tableSortKey, by content.

Fixture re-judged: the expandFor helper in ObjectDataTable.expandFls-7230.test.tsx waited for a second find, so it pinned the double read. It now reads the single post-definition find and asserts there is exactly one, so an ungated regression reddens instead of reading an unexpanded first call. The header of ObjectMap.invalidationRefetch-10623.test.tsx said the map issues two find calls on mount. It now points at the new pin.

Census: every registered data renderer's fetch effect

Population: renderers registered through ComponentRegistry in packages/components and packages/plugin-* whose useEffect issues a record read (find, aggregate, queryDataset). Enumerated by a scratch script over origin/main 4758b33, comparing what each query reads with the effect's dependency list. The script is not committed; this table is a reading taken once, not a live count (AGENTS.md #9). Field widgets (LookupField and siblings), app-shell pages and the useViewData hook (no in-tree renderer consumes it) are outside the population.

Renderer Reading Action
the six rows above mismatch changed here
object-grid / ObjectGrid (plugin-grid) Under-key. The $select harvest reads conditionalFormatting, rowActionDefs and bulkActionDefs (predicate operands), and $searchFields reads searchableFields. None is keyed. Measured by an uncommitted probe: adding a conditionalFormatting rule referencing industry to a mounted grid issued no read ($select stayed ['id','name']); a fresh mount with the rule selected ['id','name','industry']. Not edited. The file is on seat 2's live claims: objectui#9853 (PR #10278) and objectui#10583 (PR #10643). Handed to the seat.
list-view / ListView (plugin-list) Under-key, same harvest (conditionalFormatting, rowActionDefs, bulkActionDefs). Same probe, same reading. Not edited. The file is on objectui#9853's file surface (PR #10278, seat 2). Handed to the seat.
RelatedList (plugin-detail) The same over-key shape: expandKey, selectKey and the arity flag move when the child definition lands, so a child with expandable columns or a multi-valued relationship reads twice. Not edited. Ungated by a recorded decision, pinned as DECLARED COST in RelatedList.multiValueParentScope-7299.test.tsx. Raised as an open question in the report, not overridden here.
ObjectTimeline, ObjectGallery, ObjectKanban, ObjectCalendar, ObjectGantt (main read and quick-filter options), ObjectTree settled-schema gate; content keys; match none
ObjectChart, ObjectMetricWidget match by content keys; the effect keys on a useCallback identity (fetchData, fetchMetric) none (Acceptance notes)
ObjectPivotTable, element:number, tab-count probe (containers.tsx), record-activity, record-history, reference rail, DatasetWidget, DatasetReportRenderer, LineItemsPanel match none

Evidence

Every measured leg ran through SchemaRenderer and each package's own registration, or rendered the component directly where a sibling test already does. Vitest resolves every @object-ui/* specifier to its package's src (root vitest.config.mts alias), so no leg's resolution path goes through dist, and no rebuild leg applies.

On the base, before any source change (pins written first):

  • ObjectMap.fetchGate-10664: exit 1, 4 failed / 3 passed. The 4 are the SUBJECT cases; the green 3 are the two SETTLES cases and the CONTROL.
  • data-list.sortKey-10664 + record-picker.sortKey-10664: exit 1, 3 failed / 2 passed (3 SUBJECT red, 2 CONTROL green).
  • ObjectDataTable.fetchGate-10664: exit 1, 3 failed / 3 passed.
  • DashboardFilterBar.optionsFilterKey-10664: exit 1, 2 failed / 1 passed.
  • ObjectView.tableSortKey-10664: exit 1, 1 failed / 1 passed.

Reverse verification, fix committed first (46c5432). Each of the six source files was set to its 4758b33 blob; the mutation was proven on disk (blob equals base, not head; each new anchor counted 0). The seven pin files then ran red, exit 1, 20 failed / 10 passed. The 20 are every SUBJECT case plus the seven FLS cases, which now assert one read. The 10 are every CONTROL and SETTLES case. Restore was git checkout HEAD -- PATH under a trap … EXIT INT TERM, proven by each blob equalling HEAD and an empty git diff HEAD. The restored run exited 0, 30 / 30.

Targeted ablations, both through ablation-replace.mjs, which confirms the anchor hit and verifies the restore:

  • lookupExpandKey removed from the data table's dependency list, gate kept: exit 1, only the column-change SUBJECT red (1 failed / 5 passed). This isolates the columns under-key from the double read.
  • objectSchema removed from the map's dependency list, gate kept: exit 1, only the CONTROL red. $expand stayed ['owner'] where ['owner','region'] was due. The control can fail for the reason it exists.

Gates

The four package suites, the type-check and eslint ran on 2d48eb5, after merging origin/main 526fc11. The branch then merged origin/main 4df0f3d to give 6451600. That merge touches only app-shell and plugin-charts, no file in the four packages here. On 6451600 the seven pin files and the ratchet gates were re-run. The components suite's full run is from the f133b5c merge head. On 2d48eb5, where the merge brought only the RefreshIndicator change into components, the component files this PR edits and the refresh-indicator tests were re-run. That is a declared narrowing; CI runs the full farm.

  • 6451600, the seven pin files: exit 0, 7 / 7 files, 30 / 30 tests.
  • 6451600, exit 0: check-changeset-presence (4 changesets for 4 released packages), check-changeset-overwrite, check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:new-line-citations (0 new), check:changeset-claims, check:pending-changeset-literals, check:phantom-deps.
  • 2d48eb5, pnpm exec vitest run --maxWorkers=2 packages/plugin-dashboard/: exit 0, 135 / 135 files, 1268 / 1268 tests.
  • 2d48eb5, … packages/plugin-map/ packages/plugin-view/ plus the 12 data-list / record-picker / refresh-indicator test files in components: exit 0, 94 / 94 files, 743 / 743 tests.
  • f133b5c, … packages/components/: exit 0, 309 files passed and 1 skipped; 3032 tests passed and 17 skipped.
  • f133b5c, the consumer sample: exit 0, 10 / 10 files, 900 / 900 tests. The sample is the schema-catalog render tests, InterfaceListPage.mapConfig, widget-dom-leak-sweep, public-block-binding-reach, three ListView.map* and two app-shell drill tests.
  • 2d48eb5, pnpm turbo run type-check --filter @object-ui/plugin-map --filter @object-ui/components --filter @object-ui/plugin-dashboard --filter @object-ui/plugin-view --concurrency=2: exit 0, 22 / 22 tasks.
  • 2d48eb5, pnpm exec eslint on the 14 changed .ts/.tsx files, plain form (what each package's lint runs): exit 0, 0 errors. The warnings are the files' existing no-explicit-any and React Compiler advisories. Each new useMemo key draws the same preserve-manual-memoization advisory the filterKey beside it already draws.
  • 2d48eb5, also exit 0: check-changeset-fixed, check-changeset-no-major, check-type-check-coverage, check:element-data-source-declaration, check:handler-key-reads, check:self-import, check:unreferenced-sources, and check-governed-queue-guard --test (NOT GOVERNED, 18 paths).
  • check:changeset-claims (report-only): it names ten pending changesets that cite files this change edits. Each paragraph was re-read, and none describes the fetch gating or dependency lists changed here.

Changesets: one patch per touched package (plugin-map, components, plugin-dashboard, plugin-view), each naming its renderers and citing the card.

Acceptance notes

  • Latent AGENTS.md [WIP] Enhance every detail of the designer #10 identity keys, recorded and not changed here. A discard does not happen in this tree on its own, so none is reachable today:
    • ObjectChart keys its fetch effect on fetchData, and ObjectMetricWidget keys on fetchMetric; both are useCallback results.
    • LineItemsPanel keys on load, a useCallback over the memoised listFilterNode / orderBy.
    • ObjectGrid lists the memoised dataConfig and schemaFilter.
    • Each query's content is keyed correctly.
  • useViewData (@object-ui/react, exported, no in-tree renderer consumer) rebuilds its adapter on an inline value set's length alone, not its content.
  • The map and the data table now hold their loading placeholder while a changed object's definition settles, instead of drawing the previous object's rows. The gate closes only when the key moves, and on those branches the key is the queried object, so a closed gate always means a changed query.

Generated by Claude Code

…mount reads once and expanded (objectui#10664)

The fetch effect listed the object definition among its dependencies while a
separate effect loaded it into a local useState. The definition lands after
the first query, so every mount issued two find calls, the first without the
lookup $expand, and switching the bound object sent the new object's query
with the previous object's expansion. The definition now comes from the shared
useSettledSchema, and the object branch waits on it, the objectui#7895 /
objectui#7903 shape.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
…hen the sort they send changes (objectui#10664)

Both fetch effects put the sort on $orderby but listed only filterKey in
their dependencies, so a changed sort kept the old order until the object,
filter or limit moved. Each now keys on a sortKey, by content the way
filterKey keys the filter, so an equal sort in a fresh array is not a change.
The repeater row was folded here from objectui#10665; the record picker is
this card's census row for the same shape.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
… definition and keys on its expansion (objectui#10664)

The census row for object-data-table carried the map's shape: the definition
sat in a local useState fed by its own effect and was listed in the fetch
effect's dependencies, so every object-bound mount issued two find calls, the
first without $expand. The query now waits on the shared useSettledSchema.
Its expansion also reads schema.columns, which the dependency list did not
name, so it now keys on the expansion the columns produce, by content.

The FLS pin's helper waited for a second find, which pinned the double read;
it now reads the one post-definition find and asserts there is only one.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
…ptionsFrom.filter changes (objectui#10664)

The options effect sends optionsFrom.filter on both of its reads (the dataset
query's runtimeFilter and the fallback's $filter), but its dependency list did
not name it, so a changed option filter kept the previous options. It now keys
on the filter by content. Census row of this card.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
…t changes (objectui#10664)

The non-grid fetch falls back to schema.table.sort for its $orderby, and its
dependency list named the named-view and active-view sort sources but not this
one, so a changed table sort kept the old order. It now keys on the table sort
by content. Census row of this card.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
One patch changeset per touched package: plugin-map, components,
plugin-dashboard and plugin-view, each naming the renderers it changes.

Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
…64 table-sort pin

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 11 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/5876-one-lookup-predicate.md

  • names ObjectDataTable.tsx → packages/plugin-dashboard/src/ObjectDataTable.tsx — edited by this change

    computeLookupExpand in ObjectDataTable.tsx carried its own isLookup, byte-identical to the exported isLookupType in recordFields.tsx after objectui#5692 pointed both at @object-ui/core's EXPANDABLE_FIELD_TYPES. Nothing kept them aligned: a future edit to either — a member added, the retirement gate moved — would have re-forked the $expand decision from the predicate whose docblock claims to drive it. computeLookupExpand now calls isLookupType, which gains its first production consumer, and the module no longer imports the shared family or the retirement gate at all.

.changeset/6726-find-envelope-records-arms.md

  • names components/src/renderers/basic/data-list.tsx → packages/components/src/renderers/basic/data-list.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

  • names components/src/renderers/basic/record-picker.tsx → packages/components/src/renderers/basic/record-picker.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/6783-readprops-degenerate-config-bag.md

  • names data-list.tsx → packages/components/src/renderers/basic/data-list.tsx — edited by this change

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

  • names record-picker.tsx → packages/components/src/renderers/basic/record-picker.tsx — edited by this change

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

.changeset/6939-objectql-record-source-refinement.md

  • names plugin-map/src/ObjectMap.tsx → packages/plugin-map/src/ObjectMap.tsx — edited by this change

    Both renderers resolve their records from one of three keys, in this order — getDataConfig in plugin-map/src/ObjectMap.tsx and plugin-gantt/src/ObjectGantt.tsx: data, then staticData, then objectName. Both mirrors required objectName alone, so a document authored on staticData drew correctly and was refused by safeValidateSchema — six catalog entries, three per component.

.changeset/7070-no-invented-gantt-date-fields.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.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/7125-dashboard-empty-state-keys-retired.md

  • names ObjectDataTable.tsx → packages/plugin-dashboard/src/ObjectDataTable.tsx — edited by this change

    Not touched: table.noRows ('No rows to display') and engine.form.noRows (packages/app-shell/src/views/metadata-admin/i18n.ts, read at widgets.tsx) — two different, same-named keys in different namespaces. Nor the comments in WidgetEmptyState.tsx, DatasetWidget.tsx, ObjectDataTable.tsx and PivotTable.tsx that record WHY three widgets with three strings became one shared empty state; the packs' own comment keeps that rationale and now names the retirement instead of a row that is gone.

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

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.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/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/9618-record-source-ladder-attribution.md

  • names ObjectMap.tsx → packages/plugin-map/src/ObjectMap.tsx — edited by this change

    Their zod .describe strings and TS docs said the data → staticData → objectName ladder is resolved by getDataConfig. Neither renderer has had a function by that name since the ladder moved into @object-ui/core's shared resolveRecordSourceConfig (objectui#7632), so the text now names that function. The ladder order is unchanged, and so is every accepted document. The ObjectMapSchema text still says getDataConfig, which is true: ObjectMap.tsx keeps a local wrapper by that name that delegates to the shared ladder. The resolveRecordSourceConfig docblock in @object-ui/core now says that the text it quotes is the map faces' text.

.changeset/dashboard-expandable-family-convergence-5692.md

  • names ObjectDataTable.tsx → packages/plugin-dashboard/src/ObjectDataTable.tsx — edited by this change

    plugin-dashboard's two private copies of the reference-bearing field family now read @object-ui/core's published EXPANDABLE_FIELD_TYPES instead of restating it — LOOKUP_TYPES in recordFields.tsx and the inline disjunction inside computeLookupExpand in ObjectDataTable.tsx. Both carry the identity pin the already-converged consumers carry, so a member-identical private copy fails rather than quietly re-forking the table.

.changeset/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3060.3 KB 3104.5 KB
Main entry chunk (gzip) 148.1 KB 350 KB
Entry file index-B9i6Z3Ix.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
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) 547.33KB 130.98KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
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 (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.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.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) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.85KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 216.70KB 59.29KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.82KB 28.76KB
plugin-map (index.js) 22.85KB 7.58KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.83KB 22.01KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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) 18.27KB 6.20KB
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) 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) 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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 64516007d752947539542b48b722c3818eb70ef4

Read: card objectui#10664 (body + 4 comments) and PR objectui#10688 (body, 18-file list, check-runs) via REST; the head fetched into refs/review/pr-10688 and main into refs/review/main; diff taken against merge-base 4df0f3d9d1ca1d956dbe0c97739e991fb53a8583; AGENTS.md (版本号策略 and commandment #10) and .changeset/config.json. The os-dev-report comment's claims were not used as evidence; every judgment below is against the diff and the files at the head. No GitHub write of any kind was made.

Binding text. Triage 5835320838: 「gate the map's fetch on a settled schema (the objectui#7895 / #7903 shape), and give the repeater a stable sort key the way filterKey stabilises the filter. Pin 1 find per mount with $expand on the first call, and a sort change re-reading with the new $orderby while an equal sort does not. Census the other data renderers for the same mismatch as part of the claim.」 Claim 5835714138: the file surface is the map's and repeater's fetch effects plus 「the fetch-effect dependency lists of each other data renderer the card's census finds with the same mismatch (named in the PR); pins beside each; and changesets patch per touched package. ⛔ Not any view's error lifecycle」, and 「Clause-②: no」.

① Derived judgments

A. object-map (the named site) — RIGHT, the card's shape.

  • packages/plugin-map/src/ObjectMap.tsx:794-797: the local useState definition and its own metadata effect (base lines removed in the hunk at old 1014-1034) are replaced by useSettledSchema(recordSourceObjectName ?? '', hasInlineData ? undefined : dataSource). The key is the same object the removed effect read (resolveRecordSourceObjectName, packages/core/src/utils/record-source.ts:78-83: on the object provider it returns dataConfig.object, which is dataObjectName, the object the query names at ObjectMap.tsx:1016). Same hook and same placement as ObjectTimeline.tsx:332/468 and ObjectGallery.tsx:316/535 (the objectui#7895 / finding(plugin-list): ObjectGallery carries the same pre-gate shape objectui#7895 just removed from ObjectTimeline — 2 find calls per load #7903 shape the triage binds).
  • ObjectMap.tsx:862-865: the gate if (fetchesForItself && !objectSchemaReady) { setLoading(true); return; } sits ahead of the query on the branch that issues it; fetchesForItself (:834) is !Array.isArray(dataProp) && dataProvider === 'object', so host rows and an inline value set are not held, as the PR body says.
  • ObjectMap.tsx:1058: the dependency list keeps objectSchema (the hook's payload, one state value, equal-payload guarded at packages/react/src/hooks/useSettledSchema.ts:189-195) and adds objectSchemaReady (boolean) and fetchesForItself (boolean, a function of two members already listed). No memo identity is keyed (AGENTS.md [WIP] Enhance every detail of the designer #10). The query tuple at :870 is unchanged in membership, so the objectui#10623 silent-re-read logic still compares the same inputs.
  • The error lifecycle is untouched: the only hunks in the error region (:1007-1011) are comment text; setError sites are unchanged. The claim's ⛔ holds.

B. element:repeater (folded objectui#10665) — RIGHT. packages/components/src/renderers/basic/data-list.tsx:117 adds sortKey, a useMemo over [props.sort] returning JSON.stringify(props.sort) or the empty string, the same idiom as filterKey on :113; :168 lists it. props.sort reaches $orderby at :145. A string key by content, as the triage asks (「the way filterKey stabilises the filter」).

C. Census row element:record_picker — same mismatch, RIGHT. record-picker.tsx:109 sort = composed?.sort ?? props.sort reaches $orderby at :140; the base list had no sort. :126 adds sortKey by content; :163 lists it. Under-key of the identical shape as B.

D. Census row object-data-table — same mismatch (both directions), RIGHT. packages/plugin-dashboard/src/ObjectDataTable.tsx:678-681: the definition moves to useSettledSchema(schema.objectName ?? '', dataSource), keyed on the object the query names (:746). :775-779: the gate is scoped inside the branch that queries (schema.objectName && !boundData && no schema.data), holding loading while it waits, else fetchData(). This is the over-key the card describes (the definition landed after the first query and re-ran the effect). :688 lookupExpandKey = computeLookupExpand(schema, objectSchema).join(','): computeLookupExpand (:561-600) reads schema.columns (:572) and returns [] on a null definition (:565), so the key is a string derived from the exact expansion the query sends at :740, not from the column objects; a relabel yields the same string. :789 lists objectSchemaReady, objectSchema, lookupExpandKey. The removed schema effect (base, old 745-756) returned without settling when dataSource, objectName or getObjectSchema was absent; the hook settles on all of those. The added eslint-disable-next-line react-hooks/exhaustive-deps at :788 documents that schema.columns is read through the key.

E. Census row: dashboard filter bar options — same mismatch, RIGHT. DashboardFilterBar.tsx:305 ($filter) and :338 (runtimeFilter) both send from.filter; the base list (from?.object, from?.valueField, from?.labelField, dataSource) omitted it. :280 optionsFilterKey = JSON.stringify(from?.filter ?? null) (string), :357 lists it.

F. Census row: object-view non-grid read — same mismatch, RIGHT. packages/plugin-view/src/ObjectView.tsx:1178-1179 lowers currentNamedViewConfig?.sort || activeViewQueryInputs?.sort || schema.table?.sort to $orderby at :1236; the first two sources were listed (:1287), the third was not. :1085 tableSortKey = JSON.stringify(schema.table?.sort ?? null), :1288 lists it. The other sort/filter sources are content-stable (activeViewQueryInputs through useStableIdentity :1044, authoredFilters through useResolvedFilterSegments :1055 over schema.table?.filter / defaultFilters), so a table.sort change alone did not re-run the base effect; the key is load-bearing.

G. Stranding under the two new gates — covered. useSettledSchema.ts:198-211: no source, no key, no getObjectSchema function, or a thrown read all call settle(null), so ready flips true with def null and the gated branch proceeds unexpanded. Pinned on both renderers: ObjectMap.fetchGate-10664.test.tsx:181-199 (SETTLES ON A THROWN READ, SETTLES WITH NO SOURCE, each asserts one find and markers painted) and ObjectDataTable.fetchGate-10664.test.tsx:162-179 (same two, each asserting [null] expansion with rows on screen). The map passes undefined as the source for an inline value set (:796), which settles at once; fetchesForItself is false there anyway. The existing ObjectDataTable.refreshIndicatorLocale-10580.test.tsx (source with no getObjectSchema, expects 1+1 finds) still holds under the hook's immediate settle.

H. No identity key per render (AGENTS.md #10). Every new key is a string (sortKey x2, optionsFilterKey, lookupExpandKey, tableSortKey) or a boolean (objectSchemaReady, fetchesForItself); objectSchema is hook state, republished only on a changed payload. None is a useMemo/useCallback identity.

I. Files that must not be touched — untouched. The 18-file list contains no ObjectGrid.tsx, ListView.tsx or RelatedList.tsx; the PR body names those three rows as not edited, with seat 2's PRs objectui#10278 / objectui#10643 for the first two and the DECLARED COST pin for the third. That pin exists at main (packages/plugin-detail/src/__tests__/RelatedList.multiValueParentScope-7299.test.tsx:268-277) and its stated reason is the one the PR body reports: gating 「strands rows entirely when a DataSource has no getObjectSchema or its schema fetch rejects」. The PR body carries Fixes #10664 on its first line while naming the open rows.

J. Existing pins edited.

  • ObjectDataTable.expandFls-7230.test.tsx:119-129 (expandFor): base waited for find.mock.calls.length greater than 1 and read the last call; head waits for one find, settles 50 ms inside act, asserts toHaveBeenCalledTimes(1) and reads calls[0]. LOAD-BEARING, not a weakening: under the gate a mount issues exactly one find, so the old wait would time out and the whole FLS file would fail for a reason unrelated to FLS; the old helper pinned the double read this card removes. The seven FLS assertions (PIN 1-7, :155-228) are byte-for-byte unchanged (the diff touches only the act import and the helper). Guard direction: an ungated regression now reddens on the count, and even if a slow second call slipped past the 50 ms window, PIN 3/4/6/7 would read the unexpanded first call and fail, so the file cannot go green for the wrong reason.
  • ObjectMap.invalidationRefetch-10623.test.tsx:22-25: header prose only; the delta assertion is unchanged and still holds (1 mount read + 1 bus read).

K. New pins can fail — lit control and a negative leg, per file.

  • ObjectMap.fetchGate-10664.test.tsx (real SchemaRenderer, package registration): SUBJECT :132-140 expects exactly one find carrying ['owner']; on the base the map reads twice with the first unexpanded, so it is red there. SUBJECT :152-164 holds getObjectSchema and asserts zero find before release, sequence ['schema:store','find:store']. SUBJECT :166-179 asserts the switched object's only read carries ['manager'], never the previous object's expansion. CONTROL :201-215: a definition that changes after mount still re-reads with ['owner','region']; green on base and head, red if objectSchema leaves the dependency list (a dataSource swap keeps ready true for the same key, so the effect would read once with the old definition and never again).
  • ObjectDataTable.fetchGate-10664.test.tsx: SUBJECT :106-115 expects [['account','owner_dept']] (base gives [null, [...]]); SUBJECT :117-128 waits; SUBJECT :130-144 adds a lookup column on a mounted widget and expects a second read with the new $expand (base: schema.columns unkeyed, no re-read); CONTROL :146-160 relabels and expects no read (red for a key on column identity).
  • data-list.sortKey-10664.test.tsx and record-picker.sortKey-10664.test.tsx (both properties and dataSource binding for the picker): SUBJECT expects [ASC, DESC] $orderby after a sort change (base: no re-read); CONTROL expects no read for an equal sort in a fresh array (red for an identity key).
  • DashboardFilterBar.optionsFilterKey-10664.test.tsx: SUBJECT for both the dataset and the fallback read; CONTROL for an equal filter in a fresh object.
  • ObjectView.tableSortKey-10664.test.tsx: SUBJECT {name:'asc'} then {name:'desc'}; CONTROL equal sort, no read.

L. Asked and missing: none. Map gated; repeater keyed; both triage pins present (1 find per mount with $expand on the first call; sort change re-reads, equal sort does not); census done with rows named in the PR; a patch changeset per touched package.

M. Done and not asked: nothing beyond the surface. The four extra census rows are what the triage's 「census the other data renderers for the same mismatch as part of the claim」 and the claim's file surface explicitly cover. The two test edits are consequences of the gate. No spec, types, or export moved.

N. Observations (not defects). (1) ObjectMap.expandFls-7429.test.tsx:150-151 still carries the sentence "The object-schema effect re-fires the fetch effect once the schema lands"; its wait tolerates a single expanded call, so it passes, but the prose is now stale (not in this diff). (2) JSON.stringify at render on from?.filter and schema.table?.sort would throw on a non-serialisable value; it mirrors the existing filterKey idiom in the same files and those inputs are authored JSON. (3) A dataSource swap with an unchanged key reads once with the previous definition and again with the new one (the CONTROL asserts more than zero reads and the last expansion); that is useSettledSchema's same-key readiness, shared with timeline/gallery/view and identical on the base; not the mount count the card pins.

② Semver level

  • Four changesets, all patch: @object-ui/plugin-map, @object-ui/components, @object-ui/plugin-dashboard, @object-ui/plugin-view; each is in the fixed group of .changeset/config.json. Correct level: behaviour fixes, no API or accept-set change, no major (AGENTS.md 版本号策略 forbids major in changesets; breaking changes would be minor, and nothing here is breaking). Not a docs-only change (content/docs/** untouched), so the empty-frontmatter rule does not apply.
  • 10664-map-reads-once.md, sentence by sentence: "reads its records once per mount, and that read already carries the lookup expansion" TRUE (ObjectMap.tsx:862-865, pin). "loaded the object definition in an effect of its own and listed it among the fetch effect's dependencies" TRUE (base hunk). "The definition lands after the first query, so every mount issued two find calls, the first without $expand" TRUE (base structure; the card's measurement). "Switching the bound object also sent the new object's query with the previous object's expansion" TRUE (base: the fetch effect re-ran on dataObjectName with the old objectSchema state still in hand). "now reads the definition through useSettledSchema ... the way object-timeline (objectui#7895) and object-gallery (objectui#7903) already do" TRUE (both verified at the head). "A definition read that fails, or an adapter with no getObjectSchema, settles with no definition, so the map still loads, unexpanded" TRUE (hook :198-211, pins). "Host rows (data) and an inline value set are not held" TRUE (:834, :862).
  • 10664-data-table-filter-bar.md: "reads its rows once per mount, already expanded, and a dashboard filter re-reads its options when optionsFrom.filter changes" TRUE. "loaded the object definition in an effect of its own and listed it among the fetch effect's dependencies, so every object-bound mount issued two find calls, the first without $expand" TRUE (base hunk; computeLookupExpand returns [] on null). "now reads the definition through useSettledSchema ... its query waits until that read settles; a failed read, or an adapter with no getObjectSchema, still loads the rows, unexpanded" TRUE. "Its expansion also depends on columns, which did not re-run the read: adding a lookup column to a mounted table now re-reads with the new $expand, and relabelling a column does not" TRUE (:572, :688, pins). "The filter bar's option list sends optionsFrom.filter on both of its reads, the dataset query and the record fallback, but did not re-read when only that filter changed" TRUE (:305, :338, base list). "It now does, and an equal filter in a new object does not re-read" TRUE.
  • 10664-object-view-table-sort.md: "re-reads when table.sort changes" TRUE. "falls back to table.sort for its $orderby when neither the named view nor the active view declares a sort, but a change to table.sort alone did not re-run it, so the view kept the old order" TRUE (:1178-1179, base list). "keys the read on that sort's content, so an equal sort in a new array does not re-read" TRUE.
  • 10664-repeater-picker-sort-key.md: "re-read their rows when the sort they send changes" TRUE. "Both blocks put their sort on $orderby (the repeater's properties.sort; the picker's properties.sort or its dataSource binding's sort), but neither re-ran its read when only the sort changed" TRUE (data-list.tsx:145, record-picker.tsx:109/140, base lists). "Each now keys its read on the sort's content, the way it already keys on the filter's, so an equal sort in a new array does not re-read" TRUE.
  • Docs/README prose added by the diff: none (no content/docs/**, no README touched).

③ Boundary flags

  • PR body flags and open questions, answered: (a) "Latent AGENTS.md [WIP] Enhance every detail of the designer #10 identity keys, recorded and not changed here" (ObjectChart, ObjectMetricWidget, LineItemsPanel, ObjectGrid) — outside this card's mismatch (the query content is keyed; only a forced discard would show) and outside the claim's file surface for the grid; recording them is the right call. (b) useViewData length-only key — no in-tree renderer consumer; out of the census population; noted, not owed here. (c) "The map and the data table now hold their loading placeholder while a changed object's definition settles" — confirmed at ObjectMap.tsx:862-865 and ObjectDataTable.tsx:775-776; the gate closes only when the hook key moves, which on those branches is the queried object, so the held placeholder never masks an unchanged query. (d) The RelatedList question is raised in the report and explicitly not decided in the PR; the PR leaves the DECLARED COST decision standing, which is the correct posture for a seat-level ruling.
  • CI at the head 64516007: 42 check-runs. First read 18:12Z: 29 success, 3 skipped, 10 in progress, 0 failing. Re-poll 18:15Z: 31 success, 3 skipped, 8 in progress (the 8 test shards and Spec Main Shape Gate; Type Check succeeded), 0 failing. Second re-poll: see the line below.
  • Second re-poll 18:20Z (final): 43 check-runs, 40 success, 3 skipped, 0 in progress, 0 failing. All eight test shards, Type Check and Spec Main Shape Gate concluded success, so the seven pin files and the edited expandFls-7230 file are green at the head.
  • Draft: YES (draft: true).
  • Clause-②: no holds: no file under packages/types, packages/spec or any index.ts barrel is touched; useSettledSchema was already exported from @object-ui/react at main (packages/react/src/hooks/index.ts:54); no accept set or published export moves.
  • Main under the PR's files since the merge-base: NO. The merge-base is 4df0f3d9d1ca1d956dbe0c97739e991fb53a8583, which is the current origin/main; the branch's last commit 6451600 is a merge of that main.

Implemented-by: claude/issue-10664-fetch-deps-match-query
Reviewed-by: session_01KUxVUa7e39aNjhkKi1gsoy

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 18:23
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 7afc81d Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10664-fetch-deps-match-query branch September 25, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants