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
Conversation
… 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
|
changeset-claim-re-read
|
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
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_decisionbefore any scope widening: this PR touches neither the react-page scope nor its guide.Part of, not a closing keyword: member 3 (thekind: 'react'page scope) measured shipped readers and stops atneeds_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
@object-ui/plugin-view.ObjectView's non-grid fetch effect (the one commented "Fetch data for non-grid view types") names theuseDataInvalidationnonce forschema.objectName. The hook sits directly above that effect, and the nonce is the last entry of the effect's dependency list. No second channel, no remount, no key change.@object-ui/plugin-dashboard.SelectFilterinDashboardFilterBarnames the nonce foroptionsFrom.objectin the options effect, on both reads (the dataset GROUP BY and the client-sidefindfallback). The effect never resetsdynamicOptionsand never writes the dashboard variable, so the options stay on screen until the re-read answers and the selected value is kept.patchchangeset per package:10887-object-view-non-grid-bus-readerand10887-dashboard-filter-options-bus-reader.renderContent(PR fix(plugin-view): the host delegation reads a named view's remaining protocol members off the named view (objectui#10758) #10884's region),PageView(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), the react-page scope andreact-pagesguide.H1 — reproduced on base
35d68c4ce5, through the realSchemaRendererEach block is rendered in the page-region shape (
{ type, properties }forobject-view, adashboardnode withglobalFilters) underSchemaRendererProvider, over a data source that counts reads and implements noonMutation(a page action over raw HTTP announces none).'*'object-viewkanban, calendar, gallery, timeline, map, gantt — basefinddashboardoptionsFromfilter, dataset read — basequeryDatasetfindfallback — basefindBase 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(everyobject-viewtype) andthe 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
useDataInvalidationreader moves to 1 on every event (asserted before the count), a gridobject-viewre-reads once throughObjectGrid's own reader, and anobject-metricblock beside the dashboard re-reads once through its own.H2 — seam
useDataInvalidation(fetchDrawsView && dataSource ? schema.objectName || undefined : undefined), wherefetchDrawsViewis "no hostrenderListView, and the view type is none ofgrid,tree,chart". Effect: the non-grid fetch, dependency list extended byinvalidationNonce.dataChangeMatches(change, schema.objectName)with no record id: true for'*', and for any change whoseobjectNameequals the view's, record-scoped or not. False for another object.renderListViewandgridare excluded becauseListViewandObjectGridread the bus themselves, and the effect body returns early for both anyway.treeandchart(host-only, reachable only through aviewsprop) are excluded because their renderers query for themselves and read the bus themselves:ObjectTreeruns its own query ahead of the rows handed to it (objectui#10778) and re-queries whenever that array changes, andObjectChartnever reads the rows. Subscribing there would add a read per event beside theirs (ablation 3 showsfind1 → 2 for each). This narrows the card's parenthetical list, which namestreeamong the fetch-driven views: the fetch does not drive the tree's rows once a data source is present.useDataInvalidation(from && dataSource && (queryDataset or find is a function) ? from.object || undefined : undefined). Effect: theoptionsFromoptions effect, dependency list extended byinvalidationNonce.dataChangeMatches(change, optionsFrom.object): true for'*'and for any change to that object. False for another object.optionsand nooptionsFromdoes 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/main35d68c4ce5and objectstackorigin/main862b6ce869, over examples, docs, skills and templates.kind: 'react'pages that ship: 3, all in objectstack'sexamples/app-showcase(showcase_crm_workbench,showcase_renewals_pipeline,showcase_task_desk). objectui ships none inexamples/; its react pages live in thereact-pagesguide and the console's SDUI preview harnesses.useAdapter: 2 of 3.showcase_crm_workbench: the KPI strip readsadapter.find('showcase_project', { $top: 200 })in an effect keyed on the page's ownreloadKey, bumped only by its ownObjectFormonSuccess.showcase_renewals_pipeline: twoadapter.findreads (showcase_project,showcase_invoice) in an effect keyed on the selected account.showcase_task_desk: nouseAdapterread.react-pagesguide ("Live data" teachesadapter.find(...)in an effect keyed on[adapter], and the record-family table teachesuseAdapter().findOne); objectstackcontent/docs/ui/react-pages.mdx; objectstackskills/objectstack-ui/rules/pages.md.useAdapterpattern 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 injectinguseDataInvalidationinto the author scope widens a public surface. Member 3 stops atneeds_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) andDashboardFilterBar.busReread-10887.test.tsx(plugin-dashboard), both through the realSchemaRendererand each package's own registration. The inner non-grid views ofobject-vieware stand-ins registered in the real registry (plugin-view does not depend on the plugins that register them, and the rows areObjectView's read); each carries an instance id, so a remount would show.'*', a second after its own object, none after an unrelated object; forobject-view, the re-read rows reach the view and the view keeps its instance.object-viewwith no object and a filter with nooptionsFromread nothing on mount or on'*'; host-composedtreeandchartviews 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 fromHEADafterwards, blob equal andgit diff HEADempty):Tests 17 failed | 6 passed (23), the 6 being the controls.Ablations (each through
ablation-replace: anchor hit once, blob changed, restored to theHEADblob,git diff HEADempty)ObjectView's fetch dependency listinvalidationNonce,Tests 12 failed | 11 passed (23): only member 1's rows (6 types x 2)SelectFilter's dependency list, invalidationNonce]);Tests 5 failed | 18 passed (23): only member 2's rowstree/chartexclusioncurrentViewTypecomparisonsTests 2 failed | 21 passed (23): only the two host-only controls,find1 → 2 eachThe pins import their subjects by relative path and
@object-ui/reactthrough the root vitest alias, so nodist/sits between the mutation and the run.Consumer sweep
plugin-viewsuite (under the verify lock):Test Files 53 passed (53),Tests 527 passed (527).plugin-dashboardsuite (under the verify lock):Test Files 147 passed (147),Tests 1339 passed | 6 skipped (1345).object-viewor adashboardwithglobalFilters, 9 files in one run:Test Files 9 passed (9),Tests 657 passed (657):timelineAxisRefusalReach-7070(console),catalog-gallery-renderandplugin-dashboard-gallery-render(schema-catalog),rowClickModifierPayloadForward-9462,ObjectView.densityModeThroughFold-7924,ViewPreview.optionsFold-7928,StudioDesignSurface.gridRefresh,DashboardWidgetInspector(app-shell),DashboardDesignPage.presetAlias(plugin-designer).main: none (0 test files underapps/orpackages/app-shellname 10519, at base and atorigin/main6a7f24e92c; the same grep overpackages/test files at base finds the 11 earlier reader pins, so the pattern is live).a70c7607c4. Every later commit touches only the two pin files, which were re-run at56354a1c38.Gates (head
56354a1c38)type-checkfor both packages (echoed astsc --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 --testover the 6 changed paths:NOT GOVERNED.--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.tsx60,DashboardFilterBar.tsx18, 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 lintis CI's.turbo run buildover the^...closures of both packages, 16 tasks), becausetype-checkreads the built.d.ts.Serial
35d68c4ce5, which is this branch's base).git merge-tree --write-treeof this head against that PR's head5dc80e3998and againstorigin/main6a7f24e92c: both clean.ObjectViewhunks are the@object-ui/reactimport, the hook above the non-grid fetch effect, and that effect's dependency list, all aboverenderContent.DashboardFilterBarat claim time (claim record).Acceptance notes
object-view(kanban, plus calendar, gallery, timeline, map, gantt, all measured here) and for a dashboard with anoptionsFromfilter can now be green without the remount. The react-page row waits on the member 3 decision.renderContentcomment onidentityKeystill says the data-fed views are re-read "whenrefreshKeymoves". 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.treeandchart,ObjectViewstill runs its non-grid fetch on mount and onrefreshKeyalthough 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