Skip to content

fix(plugin-view,plugin-dashboard): object-view's non-grid views and a dashboard filter's optionsFrom options re-read on the data-invalidation bus (objectui#10887) - #10898

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10887-bus-readers-sixth-batch
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10887-bus-readers-sixth-batch

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #10887
Clause-②: no — members 1 and 2 add a re-read on the canonical bus to two existing readers and change no declared contract. Member 3 stops at needs_decision before any scope widening: this PR touches neither the react-page scope nor its guide.

Part of, not a closing keyword: member 3 (the kind: 'react' page scope) measured shipped readers and stops at needs_decision, as the dispatch order rules for that branch. Members 1 and 2 land here, and the card stays open for member 3.

What changed

H1 — reproduced on base 35d68c4ce5, through the real SchemaRenderer

Each block is rendered in the page-region shape ({ type, properties } for object-view, a dashboard node with globalFilters) under SchemaRendererProvider, over a data source that counts reads and implements no onMutation (a page action over raw HTTP announces none).

block mount after '*' after an unrelated object after its own object
object-view kanban, calendar, gallery, timeline, map, gantt — base 1 find 1 1 1
same — head 1 2 1 2
dashboard optionsFrom filter, dataset read — base 1 queryDataset 1 1 1
same, find fallback — base 1 find 1 1 1
both reads — head 1 2 1 2

Base failure text, quoted: the rows never re-read after the bus reported a change: expected "vi.fn()" to be called 2 times, but got 1 times (every object-view type) and the options never re-read after the bus reported a change: expected "vi.fn()" to be called 2 times, but got 1 times (both option reads).

Lit controls, green on base and head in the same tree: a bare useDataInvalidation reader moves to 1 on every event (asserted before the count), a grid object-view re-reads once through ObjectGrid's own reader, and an object-metric block beside the dashboard re-reads once through its own.

H2 — seam

  • Member 1. Hook: useDataInvalidation(fetchDrawsView && dataSource ? schema.objectName || undefined : undefined), where fetchDrawsView is "no host renderListView, and the view type is none of grid, tree, chart". Effect: the non-grid fetch, dependency list extended by invalidationNonce.
    • dataChangeMatches(change, schema.objectName) with no record id: true for '*', and for any change whose objectName equals the view's, record-scoped or not. False for another object.
    • No object or no adapter: the hook does not subscribe, the nonce stays 0, and the effect's own early return reads nothing (pinned).
    • renderListView and grid are excluded because ListView and ObjectGrid read the bus themselves, and the effect body returns early for both anyway.
    • tree and chart (host-only, reachable only through a views prop) are excluded because their renderers query for themselves and read the bus themselves: ObjectTree runs its own query ahead of the rows handed to it (objectui#10778) and re-queries whenever that array changes, and ObjectChart never reads the rows. Subscribing there would add a read per event beside theirs (ablation 3 shows find 1 → 2 for each). This narrows the card's parenthetical list, which names tree among the fetch-driven views: the fetch does not drive the tree's rows once a data source is present.
  • Member 2. Hook: useDataInvalidation(from && dataSource && (queryDataset or find is a function) ? from.object || undefined : undefined). Effect: the optionsFrom options effect, dependency list extended by invalidationNonce.
    • dataChangeMatches(change, optionsFrom.object): true for '*' and for any change to that object. False for another object.
    • A filter with authored options and no optionsFrom does not subscribe and reads nothing (pinned).

H3 — member 3 census (the kind: 'react' page scope)

Enumerated from the committed trees, not a working tree: objectui origin/main 35d68c4ce5 and objectstack origin/main 862b6ce869, over examples, docs, skills and templates.

  • Population: kind: 'react' pages that ship: 3, all in objectstack's examples/app-showcase (showcase_crm_workbench, showcase_renewals_pipeline, showcase_task_desk). objectui ships none in examples/; its react pages live in the react-pages guide and the console's SDUI preview harnesses.
  • Shipped pages that read data through useAdapter: 2 of 3.
    • showcase_crm_workbench: the KPI strip reads adapter.find('showcase_project', { $top: 200 }) in an effect keyed on the page's own reloadKey, bumped only by its own ObjectForm onSuccess.
    • showcase_renewals_pipeline: two adapter.find reads (showcase_project, showcase_invoice) in an effect keyed on the selected account.
    • showcase_task_desk: no useAdapter read.
  • Taught: the objectui react-pages guide ("Live data" teaches adapter.find(...) in an effect keyed on [adapter], and the record-family table teaches useAdapter().findOne); objectstack content/docs/ui/react-pages.mdx; objectstack skills/objectstack-ui/rules/pages.md.
  • Lit control: the same useAdapter pattern hits the scope's injection site in @object-ui/components' react-page renderer 7 times.

Verdict: a shipped and taught react page reads data through the injected useAdapter, so injecting useDataInvalidation into the author scope widens a public surface. Member 3 stops at needs_decision, with the four-axis analysis in the dev report on the card. Nothing is injected and the guide is not edited here.

Pins, red on base and green on head

ObjectView.busReread-10887.test.tsx (plugin-view) and DashboardFilterBar.busReread-10887.test.tsx (plugin-dashboard), both through the real SchemaRenderer and each package's own registration. The inner non-grid views of object-view are stand-ins registered in the real registry (plugin-view does not depend on the plugins that register them, and the rows are ObjectView's read); each carries an instance id, so a remount would show.

  • Per member, per view type or read: one read on mount, a second after '*', a second after its own object, none after an unrelated object; for object-view, the re-read rows reach the view and the view keeps its instance.
  • Member 2: the value chosen through the real select survives the re-read on the same trigger node, and the re-read options reach the list.
  • Controls: an object-view with no object and a filter with no optionsFrom read nothing on mount or on '*'; host-composed tree and chart views are not re-read by this fetch; the two lit controls above.

Final head 56354a1c38: Tests 23 passed (23). The same committed pins over base's two source files (restored from HEAD afterwards, blob equal and git diff HEAD empty): Tests 17 failed | 6 passed (23), the 6 being the controls.

Ablations (each through ablation-replace: anchor hit once, blob changed, restored to the HEAD blob, git diff HEAD empty)

ablation anchor result
1: drop the nonce from ObjectView's fetch dependency list invalidationNonce, Tests 12 failed | 11 passed (23): only member 1's rows (6 types x 2)
2: drop the nonce from SelectFilter's dependency list , invalidationNonce]); Tests 5 failed | 18 passed (23): only member 2's rows
3: drop the tree / chart exclusion the two currentViewType comparisons Tests 2 failed | 21 passed (23): only the two host-only controls, find 1 → 2 each

The pins import their subjects by relative path and @object-ui/react through the root vitest alias, so no dist/ sits between the mutation and the run.

Consumer sweep

  • plugin-view suite (under the verify lock): Test Files 53 passed (53), Tests 527 passed (527).
  • plugin-dashboard suite (under the verify lock): Test Files 147 passed (147), Tests 1339 passed | 6 skipped (1345).
  • Render-level consumers outside the two packages that mount object-view or a dashboard with globalFilters, 9 files in one run: Test Files 9 passed (9), Tests 657 passed (657): timelineAxisRefusalReach-7070 (console), catalog-gallery-render and plugin-dashboard-gallery-render (schema-catalog), rowClickModifierPayloadForward-9462, ObjectView.densityModeThroughFold-7924, ViewPreview.optionsFold-7928, StudioDesignSurface.gridRefresh, DashboardWidgetInspector (app-shell), DashboardDesignPage.presetAlias (plugin-designer).
  • objectui#10519 census test on main: none (0 test files under apps/ or packages/app-shell name 10519, at base and at origin/main 6a7f24e92c; the same grep over packages/ test files at base finds the 11 earlier reader pins, so the pattern is live).
  • The suites ran at a70c7607c4. Every later commit touches only the two pin files, which were re-run at 56354a1c38.

Gates (head 56354a1c38)

  • type-check for both packages (echoed as tsc --noEmit && tsc -p tsconfig.test.json, the second program includes the pins): Done, Done.
  • check:control-bytes, check:new-line-citations (0 new citation(s)), check:phantom-deps, check:unused-deps, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:shell-escape-residue, changeset:check, check-changeset-presence (4 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)): all exit 0.
  • check-governed-queue-guard --test over the 6 changed paths: NOT GOVERNED.
  • ESLint on the 4 changed files, --no-inline-config --format json: 4 files, 0 errors; the pins carry 0 warnings. The two source files carry the same warnings per rule on base and head (ObjectView.tsx 60, DashboardFilterBar.tsx 18, read by linting each file's base blob through --stdin-filename). A narrowed run, not the repo lint: the root config enables no type-aware linting, so this diff cannot move a verdict on an untouched file. pnpm lint is CI's.
  • Dependency closure built first (turbo run build over the ^... closures of both packages, 16 tasks), because type-check reads the built .d.ts.

Serial

Acceptance notes

  • What this unblocks for objectui#10519 (round 4 of PR fix(app-shell): PageView refreshes a page action’s data in place on the invalidation bus instead of remounting the page (objectui#10519) #10777): census rows for object-view (kanban, plus calendar, gallery, timeline, map, gantt, all measured here) and for a dashboard with an optionsFrom filter can now be green without the remount. The react-page row waits on the member 3 decision.
  • The renderContent comment on identityKey still says the data-fed views are re-read "when refreshKey moves". That is now incomplete (they also re-read on the bus) but not false, and it sits in the region this PR was told not to touch. Carrier: none.
  • Observation, not filed: for the host-only tree and chart, ObjectView still runs its non-grid fetch on mount and on refreshKey although neither renderer draws those rows. That is a read nobody uses; it predates this change and this change adds no read to it. Carrier: none.

Generated by Claude Code

… dashboard filter's optionsFrom options re-read on the data-invalidation bus (objectui#10887)

ObjectView's non-grid fetch effect now names the useDataInvalidation nonce
for schema.objectName, subscribed exactly when that effect queries (no host
renderListView, not the grid, an object and an adapter). SelectFilter's
optionsFrom read names the nonce for optionsFrom.object; the options stay on
screen until the re-read answers, and the selected value (the dashboard
variable) is never written by the read.

Pins through the real SchemaRenderer: one read on mount, one more after '*',
one more after the view's own object, none after an unrelated object, the
same inner instance, the selected filter value kept, and a control with no
object that never reads.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…re drawn; changesets (objectui#10887)

The host-only tree and chart renderers query for themselves and read the
bus themselves (ObjectTree runs its own query ahead of the rows handed to it
and re-queries when that array changes; ObjectChart never reads them), so
the non-grid fetch no longer subscribes for them: a re-read there would only
add reads. Gantt joins the pinned view types, and two controls pin the
host-composed tree and chart. One patch changeset per touched package.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…s call reads type-check (objectui#10887)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…d pin instead of casting to any (objectui#10887)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…ach pin as its lit control (objectui#10887)

A grid object-view (ObjectGrid reads the bus itself) and an object-metric
block move on the same event the members are pinned on, green on the base
and on the head.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@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 6a7f24e92 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1663 pending declaration(s) that publish a body (2257 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3095.6 KB 3104.5 KB
Main entry chunk (gzip) 148.5 KB 350 KB
Entry file index-Bf-SBIgl.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.58KB 6.17KB
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) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
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.34KB 9.17KB
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.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.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.11KB 62.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.01KB 28.49KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
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) 18.27KB 6.22KB
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) 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.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) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 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: 56354a1c38c67076e9e4c2faa0fe2cc713ea4de5

① Derived judgments

  • The seam holds. Both members use the earlier readers' hook, useDataInvalidation(objectName) from @object-ui/react, which matches through dataChangeMatches ('*' plus the named object).
    • There is no second channel and no remount: identityKey and renderContent are untouched, and the nonce is the last dependency of each existing effect.
    • Member 1 subscribes only for a drawn non-grid view with a dataSource and an objectName.
    • Member 2 subscribes only with optionsFrom and an adapter read.
    • A no-object view or filter does not subscribe.
  • The grid path does not double-read. The grid lit control asserts exactly one more read.
  • The tree / chart exclusion is correct, by reading the code:
    • ObjectTree subscribes on its own object (objectui#10778) and queries ahead of the host rows;
    • ObjectChart never reads the data prop and subscribes on its own object or dataset;
    • so subscribing here would only add unused reads.
  • The inner views honour non-empty host rows (kanban and calendar Array.isArray, gallery and timeline !props.data, map !Array.isArray, gantt non-empty), so they do not double-read.
  • Member 2 keeps the selected value. The effect never calls onChange or resets the options before the answer. The pin drives the real Radix select and asserts the same trigger, the same value and the new option after the re-read.
  • No loops or stale closures. Neither fetch notifies the bus. announceOwnWrite and onMutation batch into one render and one fetch, and filter and sort are read from the current render.
  • Pins:
    • they use the real object-view and dashboard registrations through the real SchemaRenderer;
    • red on base for the named reason (17 failed; the 6 green are labelled controls), green on head (23 of 23);
    • the ablations isolate each member (12, 5 and 2 rows).
  • Non-blocking:
    • (a) The changeset's "which switches off that view's own bus reader" is imprecise for a gantt whose host rows are empty. It keeps its own reader (objectui#7333), so a zero-row gantt reads twice per event; both reads are correct.
    • (b) fetchDrawsView is a deny-list, so a host views entry the renderer does not draw adds one unused read.
    • (c) A calendar re-read flashes its loading placeholder, the same as its own objectui#10572 reader.
    • (d) The identityKey comment is now incomplete.
  • Pending changesets naming these files: none becomes false.
  • The PR body: Part of #10887, member 3 stated as needs_decision with its census, and no path:line references.

② Semver level

patch for @object-ui/plugin-view and @object-ui/plugin-dashboard: no exported symbol, prop, authored key or accept set moves. Clause-②: no. ⛔ Not major.

③ Boundary flags

  • CI: not final at review time. The landing waits for every check on this head to be final and green, and re-runs git merge-tree against current main.
  • git merge-tree against main 6a7f24e92c: clean.
  • No open PR touches ObjectView.tsx or DashboardFilterBar.tsx; PR objectui#10884 is merged as this branch's base.
  • NOT GOVERNED (0 of 6 paths).
  • Draft, assigned to os-elon-musk.

Implemented-by: claude/issue-10887-bus-readers-sixth-batch
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 05:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 9f0c84a Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10887-bus-readers-sixth-batch branch September 28, 2026 05:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants