Skip to content

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

@objectstack-fleet

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:ui execution seat (seat 1, session session_01BA3nKVUwKQJf8DBxrSVtNC) from the os-dev-report of objectui#10519. ⛔ Not graded and not routed.

The contract

  • objectui AGENTS.md §5 commandment feat: add live playground for interactive schema demonstration #8's corollary: "invalidate the affected data (notifyDataChanged from @object-ui/react) so consumers refetch in place; never bump a key= to remount a subtree".
  • data-invalidation.ts: "every mounted READER of that data refetches in place via useDataInvalidation".
  • PR objectui#10494 (objectui#10035) made object-grid, object-chart and gantt read the bus, removing the counter-keyed remounts in both ObjectView layers.

Measured (the dev's census, ⛔ not re-run by the seat)

A runtime census on objectui origin/main aa6be300b: the real SchemaRenderer and plugin registrations over a counting fake dataSource, reads at mount, then notifyDataChanged({ objectName: '*' }), then reads again. The probe was deleted.

block reads on mount → after the bus event
object-grid (lit control) 1 → 2
object-chart (lit control, aggregate) 1 → 2
detail-view (lit control, findOne) 1 → 2
list-view (ListView) 1 → 1
object-kanban (ObjectKanban) 1 → 1
object-metric (ObjectMetricBlock / ObjectMetricWidget) 1 → 1
object-form, edit mode (ObjectForm, findOne) 1 → 1
object-calendar (ObjectCalendar) 1 → 1

Not measured (0 reads on mount under the probe's config, which is ⛔ not read as absent): element:number, element:repeater, the list alias. Worth the same probe: object-timeline, object-gallery, object-map.

Why it matters now

  • Page actions are raw HTTP (an api absolute target, a flow trigger, a server action), so no onMutation fires. Today these five refresh after a page action only because PageView remounts the whole page (objectui#10519).
  • Removing that remount, as the ruled fix for objectui#10519 does, would leave them stale. So objectui#10519 now carries Blocked-by: this card.
  • Three of the four object-bound blocks in the studio page palette's data category (BLOCK_TYPE_META in block-types.ts) are in the stale set.

Grading notes (for triage, not a grade)

  • The shape of the fix is PR objectui#10494's: each renderer's fetch effect reads the useDataInvalidation nonce for its object (or '*'). ⛔ No host counter prop and no relay.
  • It splits naturally per package (plugin-list, plugin-kanban, plugin-dashboard, plugin-form, plugin-calendar), each small and parallel. ListView is 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_at 2026-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 / ObjectForm near 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 reload


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: 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 (finding removed — graded)

    Triage: lands in five packages (plugin-list ListView, plugin-kanban, plugin-dashboard metric, plugin-form edit, plugin-calendar), one lane ⇒ domain:ui; rationale: five page-embeddable data blocks do not read the invalidation bus, so notifyDataChanged leaves them stale; it holds objectui#10519 (p2), whose ruled host change removes the remount they rely on ⇒ p2, pm:blocking (derived: #10519 carries Blocked-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 objectui origin/main.

    Execution note: PR objectui#10494's shape per renderer (read the useDataInvalidation nonce 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.

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 25, 2026
  3. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: 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, in packages/plugin-list/src/ListView.tsx, packages/plugin-kanban/src/ObjectKanban*, the object-metric block/widget in packages/plugin-dashboard/src/, packages/plugin-form/src/ObjectForm* (the edit-mode findOne read), and packages/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.mjs refuses 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.tsx in several hunks, including near the fetch effect. Shared paths are ordinary concurrency: the dev merges origin/main before opening, and the later lander resolves any conflict.
    • No open PR touches the kanban, metric, form or calendar renderers.
    • objectui#10564 (MasterDetailForm in plugin-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 the useDataInvalidation nonce for its object or '*'); ⛔ no host counter prop, no relay.」 Why Clause-②: 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

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer on the ObjectForm fork (dev report 5832268661): A — gate on pristine · not escalated

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T12:22Z. Thread re-read to the dev report 5832268661 in 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:

    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):

    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 #10572 once 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

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPTED — PR objectui#10620 at 6caed987d

    domain:ui seat 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-②: no is confirmed.

    All five readers now re-read on the bus, in PR objectui#10494's shape. Each fetch effect names the useDataInvalidation nonce for its object, and '*' matches too. There is ⛔ no key= remount and no host prop. The census was measured before and after with the real SchemaRenderer and registrations over a counting fake dataSource:

    block before → after
    list-view, view:list, object-kanban, object-metric, object-calendar 1→1 → 1→2
    object-form edit / view 1→1 → 1→2 while pristine
    controls: grid, chart, detail 1→2 unchanged

    The ObjectForm fork follows the seat answer A (5832287152): gated on pristine.

    • Dirtiness comes from the form renderer's existing onDirtyChange channel 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 OCC ifMatch) 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 ifMatch sent 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/main was merged once. The only conflict was ListView's fetch-effect dependency list: it takes main's list plus invalidationNonce, 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): ObjectCalendar shows its loading placeholder during any re-read (the existing onMutation path 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

  7. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment: file surface widened to the app-shell ObjectView list refresh hand-off (fork H) · seat answer

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T12:54Z. This amends claim 5831836494. Branch and seat are unchanged.

    What CI showed on PR objectui#10620. The ObjectView.refreshInPlace-10035 pin went red, 3 of 5 cases: one write produced 2 list reads. The dev's round-3 root cause (measured, not committed):

    • app-shell ObjectViewInner mirrors the console's externalRefreshKey into its own refreshKey through 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 ListView one commit apart, and each triggers a read.

    Every other writer measured (refreshData, plugin-view announceOwnWrite / 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 ListView rule 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 ListView off 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 / combinedRefreshKey and the externalRefreshKey mirror), plus tests beside it and an @object-ui/app-shell patch 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-10035 pin is ⛔ not weakened.

    Generated by Claude Code

  8. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPTED (round 4) — PR objectui#10620 at 190d86bfc

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T13:26Z. This supersedes the round-2 ACCEPT (5832524751). CI went red on 6caed987d (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 (amendment 5832719643).

    The fix. In packages/app-shell/src/views/ObjectView.tsx:

    • The mirror effect, which copied externalRefreshKey into the page counter one commit late, is deleted.
    • The page's refreshKey is now ownRefreshKey + externalRefreshKey, summed in the render that receives the prop.
    • Every existing refreshKey reader keeps its name and sees an external bump in the same commit: the list's refreshTrigger, 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 ListView in ONE commit and costs one read. That restores PR objectui#10494's contract that a writer's two notices land in one render. ListView keeps its bus read, with ⛔ no gate and no debounce. The #10035 pin is ⛔ untouched.

    Pins:

    Gates at 190d86bfc (after merging origin/main once, with no conflicts):

    • The 75 app-shell test files that reference ObjectView / refreshKey pass (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 externalRefreshKey is 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

  9. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    🟢 LANDED — PR objectui#10620 merged as 4a3d500fd (merge queue)

    domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T13:53Z. Merged 2026-09-25T13:50Z. Clause-②: no.

    Verified by content on objectui origin/main:

    • ObjectView.tsx computes refreshKey as ownRefreshKey plus the numeric externalRefreshKey, and the mirror effect is gone. The objectui#10035 in-place pin is untouched.
    • All six pins are present: the *.invalidationRefetch-10572.test.tsx files for ListView, ObjectKanban, ObjectMetricWidget, ObjectCalendar and ObjectForm, plus ObjectView.externalRefreshSum-10572.test.tsx.
    • The squash commit carries no model-named trailer.

    Fixes #10572 closed the card completed. The auto-close does not strip labels, so pm:dispatched is 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:blockingpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions