Skip to content

fix(plugin-view): ObjectView fetches only for views that draw the rows, and a re-read keeps them on screen (objectui#10982) - #11049

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10982-view-reread-allowlist
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10982-view-reread-allowlist

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10982

Clause-②: no

What this changes

ObjectView fetches the rows for its non-grid views itself and hands them to the inner view as data. Both halves of the card land in packages/plugin-view/src/ObjectView.tsx, which is the producer; no other package moves.

(b) The fetch runs only for view types whose renderer draws the rows

fetchDrawsView was a deny-list (grid, tree, chart) that gated only the data-invalidation subscription, and the fetch itself was gated on grid alone. There is now one allow-list, VIEW_TYPES_DRAWING_FETCHED_ROWS = kanban, calendar, gallery, timeline, map, declared beside the host-composition view-type record. It gates both the subscription and the fetch.

The list comes from reading each generateViewSchema case AND the registered renderer that case resolves to. I did not copy it from the card:

  • object-kanban, object-calendar and object-map spread the data prop into their component, and object-gallery / object-timeline read props.data. Each one draws the array it is handed.
  • object-gantt's registered wrapper (ObjectGanttRenderer) passes ObjectGantt the schema and nothing else. The chart never sees the array and queries for itself. plugin-gantt already pins this (ObjectGantt.hostDataProp-7210.test.tsx), and ListView's surfaceDrawsFetchedRows rests on the same measurement. This is where the card's premise was incomplete. The old deny-list and the comment beside it both treated gantt as data-fed, so every gantt object-view was read on mount and again on every bus event, and nothing drew those rows.
  • object-tree runs its own full query before it looks at any rows it is handed (objectui#10778). object-chart never reads them.
  • page, list and detail are ViewType members with no generateViewSchema case. They fall through to ObjectGrid, which queries for itself.

Every renderer outside the list reads the bus itself. Every site that moves refreshKey also notifies the bus (announceOwnWrite and the onMutation subscription), so none of them loses its refresh.

Scope, stated plainly. The card names the bus subscription. The same allow-list now also gates the mount / refreshKey fetch, because that read has the same defect: rows that nothing draws. The dispatch route also asked that "a new view type fetches only when its case is added to both". Measured on the base (the reverse-verification run below): tree and chart were read once on mount, and gantt, page, list and detail were read on mount and again on the bus event. The tree cost more than that. With a real object-tree under ObjectView, the base made 3 reads: one by ObjectView, plus two by the tree itself, because the rows arriving changed the tree's data dependency and re-ran its query. At this head it makes 1, the tree's own. That was a one-time probe with both legs restored and verified by blob hash; it is not a permanent test.

(c) A re-read keeps the rows on screen

The fetch used to set loading to true on every run. Each run now names its request: object, resolved filter, sort and expansion. The rest of the find params are constant. If a run's request equals the one that the rows in data answer, it is a re-read: it leaves loading false and replaces the rows when the new ones arrive. Any other run sets loading true as before. That covers a first load, and also a change of filter, sort, expansion or object.

  • loading is set on every run, false included. So a re-read that supersedes a first load still in flight shows the rows it already has, not a placeholder the superseded run left behind.
  • No fallback hides an error. A failed re-read is handled exactly like a failed first load: console.error, loading ends false, and the rows already drawn stay.
  • The find params stay an inline object literal. The first draft extracted them into a variable, which would have taken this call out of no-unprefixed-query-params (that rule reads only an inline literal) and out of the QueryParams excess-property check. Commit 1d75b57b5 put them back and keys the request on the inputs instead.

Which renderers honour a loading prop from this component: object-calendar (only alongside an array data, as its registration declares) is the one that swaps rows for a placeholder. ObjectKanban stores it but no render path reads it, and the gallery, timeline and map ignore it.

Pins

  • New: ObjectView.fetchAllowList-10982.test.tsx, 16 tests, through the real SchemaRenderer and registry:
    • For each of the five drawing types (control): read on mount, re-read exactly once on a bus event.
    • For each of gantt, tree, chart, page, list and detail: never read by this component, on mount or on a bus event, while the bare bus reader beside it moves. The inner view or the grid stand-in is asserted rendered and the schema read settled, so the zero is not vacuous.
    • Calendar: a bus re-read never paints the placeholder; the first load does (control).
    • Calendar: a re-read after an onMutation write never paints the placeholder.
    • Control: a changed request (a new filter) still paints it.
    • Calendar: a failed re-read logs the rejected error, keeps the rows and paints no placeholder, and the next event re-reads normally.
    • Control: a failed first load is logged and does not leave the placeholder up.
  • Corrected: ObjectView.busReread-10887.test.tsx. Its stand-in drew the handed rows for gantt, which the real wrapper never forwards, so gantt leaves the re-read list and becomes a "not re-read" control through the authored path.
  • Corrected: ObjectView.refreshInPlace-10035.test.tsx. Its stand-in modelled tree as drawing the handed rows. tree moves to the self-fetching group and keeps the same three assertions: same instance, refetched rows, one refetch per write.

Reverse verification (one-time; no permanent test)

With the pins committed at 3e312c6a8, the base ObjectView.tsx (blob 08747a478) was written over the file and confirmed by blob hash. The three pin files were then run, and the file was restored with git checkout HEAD -- under a trap and verified (blob equals HEAD's, git diff HEAD empty). The prediction was written down before the run: 9 red in the new file, the gantt control red in busReread-10887, and refreshInPlace-10035 all green (it is a model correction and does not discriminate). Observed: 10 failed / 31 passed, exactly those.

  • the gantt view was read for rows its renderer never draws: … called 2 times, and similarly tree 1, chart 1, page 2, list 2, detail 2.
  • the bus re-read swapped the rows on screen for the placeholder: expected [ { placeholder: true, rows: 1 } ] to deeply equal []
  • The same { placeholder: true, rows: 1 } paint on the onMutation re-read and on the failed re-read.

ObjectView.tsx is imported by relative path in these suites, so no build sat between the mutation and the run.

Gates (all read at HEAD 1d75b57b5, worktree clean)

  • pnpm exec vitest run packages/plugin-view/: Test Files 59 passed (59), Tests 584 passed (584).
  • Consumers, including every test outside the package that reads ObjectView.tsx from disk or mounts an object-view. The package graph cannot see those. PageView.refreshInPlace-10519 (a real calendar/kanban/gallery object-view re-read on the bus), timelineAxisRefusalReach-7070, objectViewRouteParity.fieldOrder-10885, rowClickModifierPayloadForward-9462, ViewPreview.optionsFold-7928, ViewPreview.routeTwoGridMembers-10885, catalog-gallery-render, the five packages/types source readers, and the four scripts/__tests__ readers: Test Files 16 passed (16), Tests 1211 passed (1211).
  • pnpm --filter @object-ui/plugin-view type-check (tsc --noEmit && tsc -p tsconfig.test.json), after turbo run build --filter='@object-ui/plugin-view^...' (15/15 tasks): exit 0. --listFilesOnly confirms that all three edited test files are in the test program.
  • pnpm exec eslint --no-inline-config on the 4 changed files: 0 errors. Warnings are unchanged per file against the base (60/60, 0/0, 4/4; the new file has 0). This is a declared narrowing of pnpm lint, backed by three pieces of evidence. Population: the config's files: ['**/*.{ts,tsx}'] block covers all four. Count: 4 files, read from the --format json output. Invariance: eslint.config.js sets no parserOptions.project / projectService, so linting is not type-aware and the custom rules are per-file AST rules, which means this diff cannot move a verdict on an untouched file.
  • node scripts/check-changeset-presence.mjs ✅. check:control-bytes, check:new-line-citations (0 new), check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:handler-key-reads, check:i18n-keys, check:unreferenced-sources, check:phantom-deps and check:unused-deps all exit 0.
  • check:changeset-claims flagged 5 pending changesets that name ObjectView.tsx (the 6726, 7070, 7499, 8653 and object-view-unmirrored-keys-7779 bodies). Each paragraph has been read: none is falsified by this diff, because they describe generateViewSchema, the grid node's title and the key census, none of which moves.
  • The full farm is CI's.

Acceptance notes

  • Observation, not filed. Read from the component's initial state, not measured in the UI. loading starts false with data as [], so a calendar handed rows paints an empty calendar for the window before the object schema settles, and only then the first-load placeholder. Base and head behave the same. Starting loading true is not a mechanical change: an object-view with no object or adapter would then sit on the placeholder forever. Out of scope here; carrier: none.
  • Same-file neighbours. The objectui#10993 branch edits the @object-ui/i18n import line, adds a top-level useObjectTranslation hook, and changes getFormTitle in this file. None of those regions is touched here. objectui#10976 is queued behind this card on the same file. Whichever lands second merges origin/main (merge commit, no rebase) and re-runs its gates.
  • ObjectView's comments that described gantt as drawing handed rows and tree as re-querying on the handed array are corrected in place. They were the text the old deny-list was reasoned from.

Changeset

.changeset/10982-objectview-reread.md, '@object-ui/plugin-view': patch.

Implemented by the domain:ui seat 2 dispatch, session https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec, branch claude/issue-10982-view-reread-allowlist.


Generated by Claude Code

…s, and a re-read keeps them on screen

The non-grid fetch and its data-invalidation subscription now run only for
the view types whose renderer draws the fetched rows: an allow-list
(kanban, calendar, gallery, timeline, map) read off each generateViewSchema
case and the renderer it resolves to. The old deny-list (grid, tree, chart)
let gantt, whose registered wrapper never hands the rows on, and every type
with no case read and re-read rows nothing draws.

A run that issues the same request as the rows on screen answer is a
re-read: it keeps those rows and replaces them on arrival instead of
setting loading, so a calendar handed rows no longer swaps to its loading
placeholder on every bus event. The first load for a request still does.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
…s rows on screen

New pin file covers both halves: every data-drawing type is read on mount
and re-read on a bus event, every self-querying or case-less type is never
read by this component, a bus or onMutation re-read never paints the
placeholder, a changed request and the first load still do, and a failed
re-read is logged with the rows kept.

The two existing pins whose stand-ins modelled gantt and tree as drawing
the handed rows now model them as the real renderers behave: gantt joins
the not-re-read controls, tree the self-fetching group.

Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec
Co-authored-by: Claude <noreply@anthropic.com>
Name the request by the inputs the query is built from, so the find call
stays an inline object literal that no-unprefixed-query-params and the
QueryParams excess-property check still read.

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 5 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/6726-find-envelope-records-arms.md

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3106.0 KB 3149.4 KB
Main entry chunk (gzip) 149.4 KB 350 KB
Entry file index-BRLHytGO.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.72KB 6.21KB
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) 558.92KB 133.97KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.01KB 66.28KB
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) 39.32KB 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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.02KB 14.87KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 165.60KB 42.64KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.75KB 22.52KB
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.16KB 39.05KB
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) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 19.75KB 6.81KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
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) 19.30KB 6.99KB
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: 1d75b57b5505398c08252c07672b7b259b8b4c36
Local-runs: none

Inputs read: card #10982 (body and all 3 comments: triage 5872794845, claim 5883738864, os-dev-report 5884410789), PR #11049 (body, 5-file list, net diff against main at the head), and the head's check-runs with filter=latest. Files read at the base or the head for context only, through the contents API: packages/plugin-view/src/ObjectView.tsx (base and head), packages/plugin-gantt/src/index.tsx, ObjectGantt.hostDataProp-7210.test.tsx, ObjectGantt.emptyHostData-7333.test.tsx, packages/plugin-tree/src/ObjectTree.tsx and index.tsx, packages/plugin-charts/src/ObjectChart.tsx and index.tsx. Nothing built, run or re-run.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong:

  1. fetchDrawsView becomes an allow-list, VIEW_TYPES_DRAWING_FETCHED_ROWS = kanban, calendar, gallery, timeline, map. RIGHT. The constant is module-private (not exported; the export roster of ObjectView.tsx is identical at base and head: FLAT_MAP_CONFIG_SPELLING, ObjectViewProps, OBJECT_VIEW_HOST_COMPOSITION_KEYS, OBJECT_VIEW_DECLARED_FORWARDED_KEYS, OBJECT_VIEW_HOST_COMPOSITION_VIEW_TYPES, ObjectView). The membership is checked against the renderers, not the card:
    • gantt OUT is right. At the base, plugin-gantt/src/index.tsx's ObjectGanttRenderer destructures { schema } alone and renders ObjectGantt with schema={bound} dataSource={dataSource} and no other prop, so the data SchemaRenderer spreads never reaches the chart. ObjectGantt.hostDataProp-7210 pins exactly this ("forwarding no other prop … the component queries for itself") and emptyHostData-7333's reach note restates it ("the registered object-gantt renderer forwards no host prop"). The base comment in ObjectView.tsx ("a gantt handed zero rows is the exception … objectui#7333") described a direct caller, not the registry path this component renders through. The card's implied list was incomplete here, as the dev says.
    • tree OUT is right. ObjectTree's fetch effect runs the object arm's own query (dataSource.find with the row ceiling and $expand) and returns before the host-rows branch (const passed = (rest as any).data ?? schema.data) is reached; (rest as any).data sits in that effect's dependency list (base line 951), which is the mechanism behind the base's extra tree query per handed array and the head's one read.
    • chart OUT is right. ObjectChartProps is closed: schema, dataSource, onSegmentClick. No data or loading read site exists.
    • page, list, detail OUT is right. generateViewSchema has no case for them, renderContent falls through to ObjectGrid, and the grid branch receives no data.
    • The five IN members are the ones the base already treated as data-fed, and nothing in the diff alters how their rows are delivered (data={data} on the SchemaRenderer, head line 2850, the ONE consumer of the data state; no callback or count exposes the fetched rows to a host).
  2. The allow-list also gates the mount / refreshKey fetch (if (!fetchDrawsView) return; replaces the grid-only early return, and fetchDrawsView joins the effect's deps). RIGHT, and beyond the card's letter: the card names the bus subscription, but its own wording is "re-reads … for rows nothing renders", and the mount read for the same six types is the same defect in the same function. Declared by the dev as a scope deviation; the new pin asserts zero reads on mount AND on the bus for each of the six, with the inner view or grid stand-in rendered and the schema read settled, so the zero is not vacuous. Every site that moves refreshKey also notifies the bus, and each self-querying renderer reads the bus itself (plugin-gantt carries ObjectGantt.invalidationRefetch-10035), so no refresh is lost.
  3. (c) setLoading(true) at the top of every run becomes setLoading(drawnRequestRef.current !== request) immediately before find, with request = JSON.stringify([objectName, finalFilter, sort, expand]) and the ref written in the same isMounted block as setData. RIGHT. Checked in the head: there is no await between the effect's early returns and the setLoading call, so it still sets in the same tick as the base did; a superseded run writes neither data nor the ref; finally still clears loading; the failure path is unchanged (console.error, loading false, rows kept). A changed filter, sort, expansion or object still paints the placeholder (pinned as a control). One consequence beyond the card's bus case: a refreshKey move with the same request (own write, onMutation) is also a re-read and keeps the rows, which is what the direction asks ("keeps the current rows on screen until the new ones arrive") and is pinned by the onMutation case. The permissions-settle second run (expand narrows, request changes) still paints the placeholder exactly as at the base; not the card's flash, not a regression.
  4. useDataInvalidation subscription now off for gantt as well as the base's tree and chart. RIGHT, by the same measurement as 1.
  5. Comments corrected at three sites and two pins corrected (busReread-10887: gantt moves from the re-read list to a not-re-read control through the registered renderer path via defaultViewType: 'gantt'; refreshInPlace-10035: tree moves to the self-fetching group with the same three assertions). RIGHT: both stand-ins modelled a renderer as drawing handed rows that the real one never draws. The card's two named lit controls stay green in the diff: the new file's first-load-paints-placeholder control, and busReread-10887's other-object / no-object controls, which no hunk touches.
  6. The find params stay an inline object literal (head lines 1423 to 1436), so no-unprefixed-query-params and the QueryParams excess-property check keep reading that call. RIGHT; the head's Lint and Type Check check-runs are success.
  7. No accept-set change. No schema, type, registry input, prop or export moves; the diff's public effect is fewer requests and no placeholder on a re-read, both inside @object-ui/plugin-view.

Gate verdicts on the head, read from the check-runs: Lint, Type Check, Build & E2E, Test (dist pins), Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Control Byte Scan, Shell Escape Residue Scan, Pre-Install Import Graph Check, README Export Check, the Doc and Skill checks, Bundle Analysis, Build Docs, Live E2E (informational): all success. dependabot and the coverage lanes: skipped (not applicable). Test (shard 1/8) through Test (shard 8/8) and Spec Main Shape Gate, in progress when this review began, all concluded success before the verdict was rendered, and the Test rollup that followed them is success: 43 check-runs on the head, none pending, none failed.

② Semver level

.changeset/10982-objectview-reread.md declares '@object-ui/plugin-view': patch. RIGHT: the diff publishes no new or changed export, prop, type, schema or registration input; both halves are behaviour fixes to a producer inside the one package. Each claim in the changeset body checks against the diff and the base: the deny-list read of gantt and the case-less types (base fetchDrawsView plus the grid-only early return), the tree's second query on a handed array (ObjectTree deps, base line 951), and the calendar flash (base setLoading(true) on every run with loading={loading} passed down). The Changeset Bump Policy, Changeset Claim Re-read and Changeset Declaration check-runs are success.

Clause-②: no — carried on the PR body and on the claim comment with its "why no" (a wasted request and a loading flash go away; no published type, schema or accept set moves). RIGHT.

③ Boundary flags

Dev flags (the os-dev-report's deviations), each answered:

  1. Scope: the allow-list also gates the mount / refreshKey fetch. ACCEPTED, judged in ①.2. The "dispatch route" sentence the dev quotes is outside this review's inputs; the judgment rests on the card's own text and the diff.
  2. gantt is OUT of the allow-list, against the card's implied list. ACCEPTED, verified at the base in plugin-gantt/src/index.tsx and its two host-data pins (①.1).
  3. Two existing pins corrected rather than kept green as written. ACCEPTED as model corrections (①.5); the controls the card names are kept.
  4. Commit e34797e55 extracted the find params; 1d75b57b5 restored the inline literal. VERIFIED at the head (①.6); intra-branch, no residue on the net diff.
  5. Attribution trailer followed AGENTS.md rather than the harness reminder. A process note, outside the contract; no bearing on this record, which is itself model-free.
  6. Lint run as a declared narrowing. SUPERSEDED by the head's Lint check-run: success.

open_questions: empty — nothing to answer or escalate.

out_of_scope_findings (one): loading starts false with data as [], so a calendar handed rows paints an empty calendar for the window before the object schema settles, then the first-load placeholder; base and head alike; carrier none, not filed. ACKNOWLEDGED as base behaviour this diff does not touch, and not a blocker. It lives in the PR's Acceptance notes as the dev left it; whether it gets a card is triage's, not this record's.

PM note acted on (the objectui#10993 branch's three ObjectView.tsx regions): VERIFIED against the hunks — none touches the @object-ui/i18n import line, a top-level useObjectTranslation hook, or getFormTitle.

Implemented-by: claude/issue-10982-view-reread-allowlist
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 05:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 30f912a Sep 29, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10982-view-reread-allowlist branch September 29, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants