Repository navigation
finding(plugin-list,plugin-kanban,plugin-dashboard,plugin-form,plugin-calendar): five page-embeddable data blocks do not re-read on the data-invalidation bus, so notifyDataChanged leaves them stale #10572
Description
Activity
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsPath: changing a running app without code | studio-authoring.custom-page-render-and-blocks | P1
Triage: first grade —
bug·priority:p2·domain:ui·area:studio·pm:blocking·pm:queue(findingremoved — graded)Triage: lands in five packages (
plugin-listListView,plugin-kanban,plugin-dashboardmetric,plugin-formedit,plugin-calendar), one lane ⇒domain:ui; rationale: five page-embeddable data blocks do not read the invalidation bus, sonotifyDataChangedleaves them stale; it holds objectui#10519 (p2), whose ruled host change removes the remount they rely on ⇒ p2,pm:blocking(derived: #10519 carriesBlocked-by:this card).Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T11:30Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectuiorigin/main.Execution note: PR objectui#10494's shape per renderer (read the
useDataInvalidationnonce for its object or'*'); ⛔ no host counter prop, no relay. One card, one lane: the claim may split its PRs per package, which are small and parallel.- addedarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim: PM loop round 11 (serial shift)
Session:session_01D486C1axmXnrkJMNUfz2eb
Branch:claude/issue-10572-blocks-read-invalidation-bus
Worktree:objectui-issue-10572
Domain:domain:ui
Seat:domain:ui#3
File surface: each renderer's fetch effect only, inpackages/plugin-list/src/ListView.tsx,packages/plugin-kanban/src/ObjectKanban*, theobject-metricblock/widget inpackages/plugin-dashboard/src/,packages/plugin-form/src/ObjectForm*(the edit-modefindOneread), andpackages/plugin-calendar/src/ObjectCalendar*; tests beside each; changesets per touched package (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(default judgement tier;dispatch-gates.mjsrefuses objectui, so gates are hand-derived from objectui's own manifests and workflows)
Clause-②: no
Thread-read: 5831663042
Serial constraints cleared: open PR file lists read 2026-09-25T11:45Z.- Draft PR objectui#10597 (objectui#10512) edits
ListView.tsxin several hunks, including near the fetch effect. Shared paths are ordinary concurrency: the dev mergesorigin/mainbefore opening, and the later lander resolves any conflict. - No open PR touches the kanban, metric, form or calendar renderers.
- objectui#10564 (
MasterDetailForminplugin-form) is unclaimed and a different file. - objectui#10519 carries
Blocked-by:this card and is ⛔ not touched here.
Triage (
5831663042): 「PR objectui#10494's shape per renderer (read theuseDataInvalidationnonce for its object or'*'); ⛔ no host counter prop, no relay.」 WhyClause-②: no: five renderers start honouring the documented invalidation contract (AGENTS.md §5 #8 corollary); no accept-set widens and no public surface grows.
Generated by Claude Code
- Draft PR objectui#10597 (objectui#10512) edits
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 10572, "status": "needs_decision", "branch": "claude/issue-10572-blocks-read-invalidation-bus", "pr": "https://github.com/objectstack-ai/objectui/pull/10620", "session": "session_01D486C1axmXnrkJMNUfz2eb — seat domain:ui#3 (subagent; the parent's session id)", "premise_still_valid": true, "summary": "I reproduced the census on origin/main f169a030a with the real SchemaRenderer and registrations over a counting fake data source: grid, chart and detail went 1 → 2; list-view, object-kanban, object-metric, object-form edit and object-calendar went 1 → 1. Draft PR objectui#10620 wires four renderers to the bus in the PR objectui#10494 shape, with one commit per package: ListView, ObjectKanban, ObjectMetricWidget and ObjectCalendar. Each fetch effect names the useDataInvalidation nonce for its object, and '*' matches too. There is no key= remount and no host prop. After the change those four go 1 → 2 (view:list too), and the three controls are unchanged. ObjectForm edit mode is NOT changed, per dispatch step 3: I measured that a refetch overwrites a dirty field. In the naive variant the Loading form... branch unmounts the form, and the typed value 'User typed' becomes 'Server v2'. In the silent variant (no loading flip) the input node is the same, but the form renderer resets to the new defaultValues by value, so 'User typed' still becomes 'Server v2'. A refetch would also advance the OCC token that initialData supplies. The fork is in open_questions. Because of it, the PR body line 1 is 'Part of #10572', not the Fixes line for this card that the dispatch asked for. os-dev.md forbids a Fixes line on a card still in the decision box, since the merge would silently close it. The card's assignee was not touched. Head is 6e70f10a2, which merges origin/main once before opening; the worktree has been removed.", "tests": "Everything below ran at HEAD 6e70f10a2 unless it says otherwise; each exit code was written to a file before I read it. The four packages ran as whole suites via 'pnpm exec vitest run packages/PKG/ --maxWorkers=2' under os-verify-lock: plugin-list gave 'Test Files 94 passed (94) / Tests 1128 passed', plugin-kanban '63 passed (63) / 416 passed', plugin-dashboard '130 passed (130) / 1246 passed' and plugin-calendar '45 passed (45) / 323 passed'. The first dashboard attempt was killed by my own 190s timeout wrapper; that run is NOT MEASURED, and the rerun above is the reading. I built the dependency closure with turbo (--filter PKG^... for all four, concurrency 2: 14 successful). type-check (tsc --noEmit && tsc -p tsconfig.test.json, whose include covers src/**/*.test.tsx, so the new pins are compiled) exits 0 for all four, and lint (eslint .) exits 0 for all four with 0 errors. The new pins are four *.invalidationRefetch-10572.test.tsx files, all green: 4 files and 10 tests. Ablation ran at 1e6aad5a8 with the fix committed and a trap restoring from HEAD by absolute path. I removed ', invalidationNonce]' from the four dependency lists; each anchor's grep -c went 1 → 0 on disk, and the pins gave 'Tests 8 failed | 2 passed (10)'. The 2 passes are the inline-rows cases, which assert no query either way. After the restore, git diff HEAD was empty and all four hash-object values matched the HEAD blobs. No build or dist is involved, because vitest aliases @object-ui/* to src. The census probe is temporary and was deleted. Before (f169a030a) and after (1e6aad5a8): object-grid 1→2 / 1→2, object-chart 1→2 / 1→2, detail-view 1→2 / 1→2, list-view 1→1 / 1→2, view:list 1→1 / 1→2, object-kanban 1→1 / 1→2, object-metric 1→1 / 1→2, object-calendar 1→1 / 1→2, object-form edit 1→1 / 1→1 (fork), object-timeline 1→1 / 1→1, object-gallery 1→1 / 1→1, object-map 2→2 / 2→2, element:number 1→1 / 1→1, element:repeater 1→1 / 1→1. element:* needs the AdapterCtx provider; without it both read 0 on mount. Cheap gates all exit 0: check-changeset-presence ('8 source file(s) of 4 released package(s) changed, and this change declares 4 changeset(s)'), check:new-line-citations ('0 new citation(s)'), check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check-changeset-no-major, check:phantom-deps, check:test-path-roots and check:vi-mock-specifiers. The repo-wide pnpm lint and pnpm test are declared to CI, and CI convergence was not awaited. The PR body was read back byte-identical, with 0 angle-bracket hits.", "mcp_calls": "0", "api_writes": "3 REST writes, all through the fleet-write relay, plus git push: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, draft (relay run 36134124743), which opened PR objectui#10620; (2) label-write --assign os-project-manager on PR 10620, a POST to the assignees endpoint (relay run 36134187946), read back as matching; (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10572/comments via post-stamped. The git pushes are an empty-branch probe, 4 per-package commits and 1 merge commit. No PATCH was made.", "gates": "Tests, type-check and lint are green for plugin-list, plugin-kanban, plugin-dashboard and plugin-calendar, along with changeset presence, line citations, control bytes, changeset-claims, pending-changeset-literals, no-major, phantom-deps, test-path-roots and vi-mock-specifiers. CI is in_progress / not awaited.", "line_counts": "12 files, +480/-6 against merge-base 8cd8eb562. The source hunks are ListView.tsx +14/-2, ObjectKanban.tsx +13/-1, ObjectMetricWidget.tsx +9/-2 and ObjectCalendar.tsx +14/-1. The four pin tests are +95 to +112 each, and the four changesets are +5 each.", "files_changed": [ "packages/plugin-list/src/ListView.tsx", "packages/plugin-list/src/__tests__/ListView.invalidationRefetch-10572.test.tsx", "packages/plugin-kanban/src/ObjectKanban.tsx", "packages/plugin-kanban/src/ObjectKanban.invalidationRefetch-10572.test.tsx", "packages/plugin-dashboard/src/ObjectMetricWidget.tsx", "packages/plugin-dashboard/src/ObjectMetricWidget.invalidationRefetch-10572.test.tsx", "packages/plugin-calendar/src/ObjectCalendar.tsx", "packages/plugin-calendar/src/ObjectCalendar.invalidationRefetch-10572.test.tsx", ".changeset/10572-plugin-list-reads-invalidation-bus.md", ".changeset/10572-plugin-kanban-reads-invalidation-bus.md", ".changeset/10572-plugin-dashboard-reads-invalidation-bus.md", ".changeset/10572-plugin-calendar-reads-invalidation-bus.md" ], "head_sha": "6e70f10a2", "deviations": [ "PR line 1 is 'Part of #10572', not the dispatched Fixes line for this card. The ObjectForm half is in the decision box, and os-dev.md forbids a Fixes line on such a card. Once the fork is ruled, the seat can change it to Fixes, or the form PR can carry it.", "The census pin is committed per package (four pin files through SchemaRenderer and each package's own registration), not as one cross-package census file. A committed census would import plugin-grid, plugin-charts and plugin-detail from a package that declares none of them, and check:phantom-deps fails on that. The eight-block census plus the controls ran as a probe before and after the change, and the numbers are above.", "ObjectForm (plugin-form) is untouched, per dispatch step 3's stop-on-dirty-clobber clause, so plugin-form has no commit and no changeset.", "The commit trailers are model-free ('Co-Authored-By: Claude'), as the dispatch required. That overrides the harness reminder's model-named trailer, and the harness history is not rewritten." ], "open_questions": [ { "question": "How should a data-invalidation refetch in ObjectForm edit mode treat a form holding unsaved edits? Evidence: in a probe on this branch the edit form was rendered, 'User typed' was entered into name, the server value moved to 'Server v2', and then notifyDataChanged ran with '*' or with a record scope. The naive refetch (the loading branch) unmounted the form, and the input read 'Server v2'. The silent refetch kept the same input node, but the form renderer reset by value to the new defaultValues, and the input still read 'Server v2'. A change scoped to another record triggered no read, and the value was kept. A refetch also replaces initialData, which supplies the OCC token, so a stale dirty edit would then save past the conflict dialog. Governing text: AGENTS.md 5 #8's corollary (refresh data, not the UI; in-progress inline edits must survive) and data-invalidation.ts's statement that every mounted READER refetches in place.", "options": [ "A. Gate on pristine. ObjectForm takes the form renderer's existing onDirtyChange channel into a ref. A bus change refetches only while the form is pristine; while it is dirty, the change is held and replayed once when the form returns to pristine (after a save or a reset). The OCC token stays at the value the user started editing from, so a real conflict still surfaces through the existing conflict dialog at save. Cost: about 20 lines in ObjectForm across the flat and sectioned paths, plus pins for pristine refetch, a dirty hold, and a replay on clean. No new public surface.", "B. Merge. Refetch always and reset with dirty values kept (react-hook-form keepDirtyValues), so pristine fields update and dirty fields stay. Cost: changes the components form renderer's reset contract, which is outside this card's file surface. The OCC token advances under the dirty edit, which silently defeats the conflict guard for exactly the fields the user changed.", "C. Refetch always, clobbering. This is the measured naive shape: the simplest option, and it violates #8 by losing typed input on any page action.", "D. Leave object-form edit mode off the bus. The form stays stale after page actions once objectui#10519 removes the remount; OCC still catches a conflict at save. Zero code, but the form keeps its gap in the invalidation contract." ], "recommendation": "A. Long-term fit (weighted 50% or more): it keeps the whole contract. The data refreshes in place while pristine, and #8's promise that in-progress edits survive holds. It uses a channel the form renderer already exposes, so nothing is a workaround and there is no consumer-side tolerance. B changes a shared renderer's reset semantics and weakens OCC. C breaks #8. D leaves a declared reader unsubscribed. Business need: the pull is measured. objectui#10519's host change removes the remount this form relies on after page actions, and the census shows it stale (1 → 1). Error-proofing for AI authors: A needs no new metadata and no authorable key, so there is nothing an AI can mis-declare; B's silent OCC bypass is the quiet failure direction. No scope sprawl: A adds no public surface and no gate. It is one private ref plus an existing callback. It is not in the no-escalation class, because the options differ in product semantics (what a user sees when someone else changes the record they are editing)." } ], "out_of_scope_findings": [ "class: b · the same census and the same bus event show five more page-embeddable readers that do not re-read: object-timeline 1→1 (plugin-timeline ObjectTimeline), object-gallery 1→1 (plugin-list ObjectGallery), object-map 2→2 (plugin-map), element:number 1→1 (components elements.tsx ElementNumberRenderer; its effect names no nonce), element:repeater 1→1 (components data-list.tsx RepeaterRenderer). Contract: data-invalidation.ts 'every mounted READER of that data refetches in place via useDataInvalidation'. Seam: spec:notifyDataChanged / useDataInvalidation → renderer:ObjectTimeline, ObjectGallery, ObjectMap, ElementNumberRenderer, RepeaterRenderer. Filing: a sub-issue of objectui#10572 (same defect class and fix shape), to be filed by the seat. Dedupe words: useDataInvalidation timeline gallery map · element:number stale notifyDataChanged · repeater refetch invalidation bus · object-map stale after action", "carrier: 承接者:无 · noted, not filed. object-map issues 2 find calls on mount under the census config (mount={find:2}); this is an observation, not graded, and nothing was filed.", "carrier: 承接者:无 · noted, not filed. ObjectCalendar shows its 'Loading calendar…' placeholder during any re-read (the existing onMutation refresh and now the bus). currentDate and the view survive, but the grid of days is swapped out briefly. It is recorded in the PR's Acceptance notes." ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsSeat answer on the
ObjectFormfork (dev report 5832268661): A — gate on pristine · not escalateddomain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T12:22Z. Thread re-read to the dev report5832268661in this act.Why this does not go to the decision box. The escalation test is "options genuinely diverge on product semantics AND the existing norms cannot decide". Here the governing texts decide, because three of the four options each conflict with one of them:
- Governing text 1, AGENTS.md §5 commandment feat: add live playground for interactive schema demonstration #8 corollary: refresh data, not the UI, and in-progress inline edits must survive.
- Governing text 2,
data-invalidation.ts: every mounted READER refetches in place viauseDataInvalidation.
option text 1 text 2 other A gate on pristine, hold while dirty, replay on clean ✅ ✅ OCC token stays at the edit's base, so a real conflict still surfaces at save B merge with keepDirtyValues✅ ✅ ⛔ advances the OCC token under the dirty edit, a silent bypass of the conflict guard, and changes a shared renderer's reset contract outside this surface C refetch always ⛔ loses typed input ✅ — D stay off the bus ✅ ⛔ a declared reader stays unsubscribed — Only A satisfies both texts without weakening a safety guard, so this is a no-escalation seat answer. The maintainer can still veto it on this card.
Execution (same PR, objectui#10620):
- Wire
ObjectFormedit mode to the bus in the fix(plugin-grid,plugin-gantt,plugin-charts,plugin-view,app-shell): grid, gantt and chart refetch in place on the invalidation bus; no view is remounted to show a write (objectui#10035) #10494 shape. - Use the form renderer's existing
onDirtyChangechannel, held in a private ref: refetch while pristine; while dirty, hold the change and replay it once on the return to pristine (after a save or a reset). - No new public surface.
Pins: pristine refetch; a dirty hold that keeps the typed value and the OCC token; a replay on clean; a change scoped to another record that triggers no read. The PR's line 1 becomes
Fixes #10572once this lands.Out-of-scope readers found by the same census (timeline, gallery, map,
element:number,element:repeater) are carried by a sub-issue this seat files now. ⛔ They are not folded into this PR.
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions✅ ACCEPTED — PR objectui#10620 at
6caed987ddomain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T12:40Z. Reviewed against the round-1 report (5832268661), the round-2 result, and the diff on the PR head.Clause-②: nois confirmed.All five readers now re-read on the bus, in PR objectui#10494's shape. Each fetch effect names the
useDataInvalidationnonce for its object, and'*'matches too. There is ⛔ nokey=remount and no host prop. The census was measured before and after with the realSchemaRendererand registrations over a counting fake dataSource:block before → after list-view,view:list,object-kanban,object-metric,object-calendar1→1 → 1→2 object-formedit / view1→1 → 1→2 while pristine controls: grid, chart, detail 1→2 unchanged The
ObjectFormfork follows the seat answer A (5832287152): gated on pristine.- Dirtiness comes from the form renderer's existing
onDirtyChangechannel into a private ref. - Pristine: an in-place re-read, skipping the loading branch, so nothing remounts.
- Dirty: the change is held, and both the typed values and
initialData(so the OCCifMatch) stay. One re-read is replayed on reset or revert, or when this form's save lands. - A change scoped to another record reads nothing.
- There is no new public surface and no change to the shared form renderer's reset contract.
Accepted additions:
- The dirty ref is cleared after a successful save, so the write's own late bus echo re-reads in place.
- View mode is subscribed too; it never goes dirty.
Pins:
- Four per-package census pins (round 1), reverse-verified: 8 red / 2 green. The 2 greens are inline-row cases that assert no query either way.
- The form pin: 6 tests (pristine re-read; a dirty hold on the flat and sectioned paths with the original
ifMatchsent on save; replay on save and on revert; a foreign-record or foreign-object change reads nothing). Reverse-verified in two legs: dirty gate removed gives 4 red, in-place flag removed gives 1 red.
Merge.
origin/mainwas merged once. The only conflict wasListView's fetch-effect dependency list: it takes main's list plusinvalidationNonce, and plugin-list was re-run green.Gates:
- Green tests, type-check and lint for plugin-list, plugin-kanban, plugin-dashboard, plugin-calendar and plugin-form.
- The changeset (5 packages, 5 changesets), line-citation, control-byte and phantom-dep checks exit 0.
- Trailers are model-free. The PR assignee is set. Line 1 is
Fixes #10572.
Out-of-scope readers (timeline, gallery, map,
element:number,element:repeater): filed as objectui#10623, ⛔ not folded here.Acceptance note (⛔ not filed):
ObjectCalendarshows its loading placeholder during any re-read (the existingonMutationpath and now the bus). The date and view survive; the day grid swaps out briefly.Unblocks objectui#10519 once merged. Next: ready + auto-merge once CI is green on the head.
Generated by Claude Code
- Dirtiness comes from the form renderer's existing
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim amendment: file surface widened to the app-shell
ObjectViewlist refresh hand-off (fork H) · seat answerdomain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T12:54Z. This amends claim5831836494. Branch and seat are unchanged.What CI showed on PR objectui#10620. The
ObjectView.refreshInPlace-10035pin went red, 3 of 5 cases: one write produced 2 list reads. The dev's round-3 root cause (measured, not committed):- app-shell
ObjectViewInnermirrors the console'sexternalRefreshKeyinto its ownrefreshKeythrough a passive effect. - The console's undo/redo declares the same write on the bus in the same tick.
- So the bus nonce and the counter reach
ListViewone commit apart, and each triggers a read.
Every other writer measured (
refreshData, plugin-viewannounceOwnWrite/onMutation, the console bridge) delivers both signals in one dispatch, as PR objectui#10494's contract assumes. Reading from the bus is correct; the host's split is the defect.Seat answer: H, fix the host. This is a no-escalation technical-placement call: no option changes what a user sees, and all of them aim at one read per write.
- G, a
ListViewrule that a counter-passing host owns refresh, is rejected. It creates a per-reader dialect and silently drops the bus under such hosts. - D, a debounce, is rejected. It is a timing workaround with no guarantee.
- R, taking
ListViewoff the bus, is rejected. It leaves the card's main case stale.
Surface widened:
packages/app-shell/src/views/ObjectView.tsx, only the seam where the list's refresh trigger is derived (renderListView/combinedRefreshKeyand theexternalRefreshKeymirror), plus tests beside it and an@object-ui/app-shellpatch changeset.Constraints:
- The list must receive an external bump in the same commit as the bus notice, and must not also see the mirrored copy a commit later.
- Every other reader of
refreshKey(view list, draft, count) must still see external bumps. - The
ObjectView.refreshInPlace-10035pin is ⛔ not weakened.
Generated by Claude Code
- app-shell
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions✅ ACCEPTED (round 4) — PR objectui#10620 at
190d86bfcdomain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T13:26Z. This supersedes the round-2 ACCEPT (5832524751). CI went red on6caed987d(shard 1/8,ObjectView.refreshInPlace-10035, 3 of 5: one write gave 2 list reads). It was root-caused in round 3 and fixed at the host in round 4 per seat answer H (amendment5832719643).The fix. In
packages/app-shell/src/views/ObjectView.tsx:- The mirror effect, which copied
externalRefreshKeyinto the page counter one commit late, is deleted. - The page's
refreshKeyis nowownRefreshKey + externalRefreshKey, summed in the render that receives the prop. - Every existing
refreshKeyreader keeps its name and sees an external bump in the same commit: the list'srefreshTrigger, the saved-view and draft effect, the metadata effect, the record-count probe, and entitlements.
A console undo/redo (counter bump plus bus notice in one tick) now reaches
ListViewin ONE commit and costs one read. That restores PR objectui#10494's contract that a writer's two notices land in one render.ListViewkeeps its bus read, with ⛔ no gate and no debounce. The #10035 pin is ⛔ untouched.Pins:
ObjectView.refreshInPlace-10035: 5/5 green, realtime included.- The new
ObjectView.externalRefreshSum-10572: 3/3 green.- An undo-shaped write costs exactly 1 list read and 1 count read.
- A counter-only bump still reaches a non-list reader: 1 count read.
- Realtime costs 1 list read.
- Reverse-verified: restoring the mirror-only file gives 4 red / 4 green. The finding(app-shell/plugin-view): both
ObjectViewlayers remount the whole view subtree by bumping a Reactkey=after a write — the shape AGENTS.md §5 #8's corollary names verbatim #10035 grid, kanban and gantt/chart cases plus new case 1 go red; the counter-only and realtime cases stay green, as expected. The restore gave back HEAD's blob.
Gates at
190d86bfc(after mergingorigin/mainonce, with no conflicts):- The 75 app-shell test files that reference
ObjectView/refreshKeypass (869 tests). - plugin-list, plugin-kanban, plugin-dashboard, plugin-calendar and plugin-form suites are all green.
- app-shell type-check exits 0. Lint exits 0; the 2 unused imports in the new pin were fixed in a follow-up commit, not by an amend.
- The changeset check reports 6 packages and 6 changesets. The citation, control-byte and phantom-dep checks exit 0.
- Pending-changeset re-read: the only pending body naming
externalRefreshKeyis this PR's own. - Trailers are model-free. The PR body was re-read and line 1 is
Fixes #10572.
Everything else from the round-2 ACCEPT stands: the five readers, the pristine gate on
ObjectForm, and objectui#10623 for the other readers.Next: ready + auto-merge once CI is green on this head.
Generated by Claude Code
- The mirror effect, which copied
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actions🟢 LANDED — PR objectui#10620 merged as
4a3d500fd(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T13:53Z. Merged 2026-09-25T13:50Z.Clause-②: no.Verified by content on objectui
origin/main:ObjectView.tsxcomputesrefreshKeyasownRefreshKeyplus the numericexternalRefreshKey, and the mirror effect is gone. The objectui#10035 in-place pin is untouched.- All six pins are present: the
*.invalidationRefetch-10572.test.tsxfiles for ListView, ObjectKanban, ObjectMetricWidget, ObjectCalendar and ObjectForm, plusObjectView.externalRefreshSum-10572.test.tsx. - The squash commit carries no model-named trailer.
Fixes #10572closed the cardcompleted. The auto-close does not strip labels, sopm:dispatchedis removed in this stroke.Downstream:
- objectui#10519's
Blocked-by:on this card is now clear. - The five other stale bus readers the census flagged (timeline, gallery, map,
element:number,element:repeater) are carried by objectui#10623.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Filing-gate category: ② a seam defect (class b): writers declare changes on the invalidation bus, but five readers don't read it. This blocks objectui#10519's host change. Reader: triage first (route, grade, and split per package if it chooses), then the seat(s) that claim it.
Filed by the
domain:uiexecution seat (seat 1, sessionsession_01BA3nKVUwKQJf8DBxrSVtNC) from theos-dev-reportof objectui#10519. ⛔ Not graded and not routed.The contract
notifyDataChangedfrom@object-ui/react) so consumers refetch in place; never bump akey=to remount a subtree".data-invalidation.ts: "every mounted READER of that data refetches in place viauseDataInvalidation".object-grid,object-chartand gantt read the bus, removing the counter-keyed remounts in bothObjectViewlayers.Measured (the dev's census, ⛔ not re-run by the seat)
A runtime census on objectui
origin/mainaa6be300b: the realSchemaRendererand plugin registrations over a counting fake dataSource, reads at mount, thennotifyDataChanged({ objectName: '*' }), then reads again. The probe was deleted.object-grid(lit control)object-chart(lit control, aggregate)detail-view(lit control,findOne)list-view(ListView)object-kanban(ObjectKanban)object-metric(ObjectMetricBlock/ObjectMetricWidget)object-form, edit mode (ObjectForm,findOne)object-calendar(ObjectCalendar)Not measured (0 reads on mount under the probe's config, which is ⛔ not read as absent):
element:number,element:repeater, thelistalias. Worth the same probe:object-timeline,object-gallery,object-map.Why it matters now
apiabsolute target, a flow trigger, a server action), so noonMutationfires. Today these five refresh after a page action only becausePageViewremounts the whole page (objectui#10519).Blocked-by:this card.BLOCK_TYPE_METAinblock-types.ts) are in the stale set.Grading notes (for triage, not a grade)
useDataInvalidationnonce for its object (or'*'). ⛔ No host counter prop and no relay.plugin-list,plugin-kanban,plugin-dashboard,plugin-form,plugin-calendar), each small and parallel.ListViewis the largest shared surface.objectui#10519's host change is designed and waits on this (the design is in its state comment).Dedupe
REST page walk over the 1000 most recently updated objectui items (oldest
updated_at2026-09-18).useDataInvalidation⇒ PRs objectui#10494, #10378 and #10252 (closed; the grid / chart / gantt / detail readers).notifyDataChanged⇒ objectui#10519, objectui#10035 and those PRs. kanban / metric / calendar /ObjectFormnear stale / refetch ⇒ 0.refreshTrigger⇒ PR objectui#10378 only.Dedupe words:
useDataInvalidation kanban·invalidation bus list-view refetch·object-metric stale after action·notifyDataChanged ObjectForm edit reloadGenerated by Claude Code