Skip to content

finding(app-shell): PageView remounts the whole page after a page action by keying InterfaceListPage / SchemaRenderer on a refreshKey counter — the AGENTS.md §5 #8 shape objectui#10035 removed from both ObjectView layers #10519

Description

@objectstack-fleet

History: this card read Blocked-by: #10887 until 2026-09-28T13:0xZ. objectui#10887 closed completed (PR objectui#10973, member 3, ruling 5864668610 letter A): the kind: 'react' page author scope now injects useDataInvalidation. With members 1 and 2 (PR objectui#10898), every reader the round-3 census named reads the bus (seat 1's unblock note 5870369205). The card is unblocked, and PR objectui#10777 goes to round 4: a base merge, the census re-run on main, then contract review. Dispatch is serial, so round 4 takes the next slot after objectui#10899 (domain:ui seat 2, session_014mXUNuFomfj24w7s1pZzhN, 2026-09-28T18:0xZ).
History: this line read Blocked-by: #10853 until 2026-09-28T02:4xZ. objectui#10853 closed completed (PR objectui#10873): object-master-detail-form's edit-mode detail lines and element:record_picker's options now read the bus. Every block the round-2 census named reads the bus, so the card is unblocked and PR objectui#10777 goes to round 3 (domain:ui seat 2, session_014mXUNuFomfj24w7s1pZzhN).
History: this line read Blocked-by: #10814 until 2026-09-27T21:5xZ. objectui#10814 closed completed (PR objectui#10836). PR objectui#10777's round-2 census (5860089413) then found two more blocks that a stored page can hold and that refresh only through the remount: object-master-detail-form's edit-mode detail lines, and element:record_picker's options. Seat ruling 5860118207 (readers first, option A) names objectui#10853 as their reader card (domain:ui seat 2, session_014mXUNuFomfj24w7s1pZzhN).

History: this line read Blocked-by: #10778 until 2026-09-27. objectui#10778 closed completed (PR objectui#10809). Under decision B (5830213285, 「readers first」) the remaining reader batch is objectui#10814, named as a second blocker in 5857953021 and 5858093825; this moves it into the body, the canonical home the unlock scan reads, so the card is not released while those two blocks are stale (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).

History: this card read Blocked-by: #10572, from seat decision B 5830213285 (2026-09-25), and then Blocked-by: #10715, from unlock scan 5851718087. Both blockers landed, and the triage seat returned the card to pm:queue (5854561985). On 2026-09-27 the contract review of PR objectui#10777 (5856227410) found three more blocks that refresh only through the remount, and seat ruling 5856239953 (readers first) set Blocked-by: #10778.

Filing-gate category: ① a product defect with a named site. Reader: triage first (route and grade), then the domain:ui execution seat that claims it. The site is packages/app-shell/src/views/PageView.tsx.

Filed by the domain:ui seat #2 (session_014mXUNuFomfj24w7s1pZzhN) at the ACCEPT of objectui#10035 (PR objectui#10494, comment 5827360925). The at-tier contract review of that PR measured it with a repo-wide grep for counter-keyed subtrees. ⛔ Filed bare, not graded here.

The defect

objectui AGENTS.md §5 commandment #8's corollary reads: "refresh data, don't rebuild UI — after a save/action, invalidate the affected data (notifyDataChanged from @object-ui/react) so consumers refetch in place; never bump a key= to remount a subtree".

PR objectui#10494 (merged 3759c568e) removed the last counter keys from both ObjectView layers. One site of the same shape remains, read on objectui origin/main:

  • PageView holds const [refreshKey, setRefreshKey] = useState(0). The comment above it says the counter is "Threaded into the page context AND used to remount the renderer."
  • ConsoleActionRuntimeProvider's onRefresh bumps it: onRefresh={() => setRefreshKey((k) => k + 1)}.
  • Both render branches are keyed on it: <InterfaceListPage key={refreshKey} …> (ADR-0047 interface mode) and <SchemaRenderer key={refreshKey} …>.

⇒ Every successful page-level action remounts the whole page. That destroys scroll, collapsed sections, tab state and in-progress edits, and it refetches every embedded block. This is the damage §5 #8 enumerates, and it is the mechanism objectui#10035 retired one layer down.

What a fix has to measure (for triage, not a ruling)

  • Which page-level actions reach onRefresh, and whether the data they change already reaches the embedded blocks through the invalidation bus. objectui#10035's refreshData now calls notifyDataChanged, so the same move may be enough here.
  • refreshKey is also threaded into the page context (context: { …, refreshKey }). Check whether any page block reads it as its own refresh input before the key is dropped.
  • Pins in the shape PR objectui#10494 used: after a page action, (a) the same renderer instance is kept, and (b) the embedded data is re-read.

Dedupe

REST page walk over the 1000 most recently updated objectui issues and PRs (the search API answers 403 here), oldest updated 2026-09-18T10:20Z. Pattern PageView … (refreshKey|key=) or InterfaceListPage … key ⇒ 0 hits. Lit control SystemHubPage ⇒ 3 hits. ⇒ No duplicate.

Dedupe words: PageView refreshKey remount · InterfaceListPage key refreshKey · page action remounts page · SchemaRenderer key counter page


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

    Triage: lands in packages/app-shell/src/views/PageView.tsx (refreshKey keyed onto InterfaceListPage and SchemaRenderer) ⇒ domain:ui; rationale: every successful page-level action remounts the whole page, destroying scroll, tab state and in-progress edits — the shape objectui AGENTS.md §5 #8 forbids and objectui#10035 removed from both ObjectView layers. User-visible loss of state ⇒ p2.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T09:01Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/main. Dedupe by this seat over 725 objectui cards (open, plus closed since 2026-09-18): refreshKey → 4 hits (this card; #7483, a different ObjectView typing card; #10383 and #10384, closed).

    Execution note: invalidate through notifyDataChanged as objectui#10035 did; check first whether any page block reads the context's refreshKey. Pins in PR objectui#10494's shape: the same renderer instance is kept, and embedded data is re-read.

  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 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10519-pageview-refresh-in-place
    Worktree: objectui-issue-10519
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/PageView.tsx (the refreshKey counter, its two key= sites, the page-context refreshKey and onRefresh only), readers of the context's refreshKey only if the measurement finds any, pins beside the existing PageView tests, and one .changeset/10519-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p2: every successful page action remounts the whole page, losing scroll, tab state and in-progress edits
    Clause-②: no
    Thread-read: 5829764540
    Serial constraints cleared: open-PR file lists read 2026-09-25T09:10Z ⇒ none names PageView.tsx, and no live pm:dispatched claim names it. The queued objectui#9673 (PageView spreads (page as any).context) touches the same file and is not claimed; this seat will not run it at the same time.

    Scope

    The triage (5829764540): 「invalidate through notifyDataChanged as objectui#10035 did; check first whether any page block reads the context's refreshKey. Pins in PR objectui#10494's shape: the same renderer instance is kept, and embedded data is re-read.」

    • Measure first: who reads refreshKey off the page context (context: { params, refreshKey }). If a block reads it, say how, and whether the invalidation replaces it.
    • Fix: onRefresh invalidates the affected data (notifyDataChanged from @object-ui/react) so consumers refetch in place. ⛔ No key= bump, and no counter-keyed subtree (objectui AGENTS.md §5 feat: add live playground for interactive schema demonstration #8).
    • Pin: after a page action's refresh, the InterfaceListPage / SchemaRenderer instance is the same (identity or a mount counter), and embedded data is re-read, with a lit control.

    ⇒ Fixes #10519.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T09:11Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10519,
    "status": "needs_decision",
    "branch": "claude/issue-10519-pageview-refresh-in-place",
    "pr": null,
    "session": "session_01BA3nKVUwKQJf8DBxrSVtNC — mode:subagent, so this is the parent PM seat session (domain:ui#1)",
    "premise_still_valid": true,
    "summary": "The defect is real and reproduced on BASE aa6be30 (origin/main): one successful page action remounts the page on BOTH branches (scratch probe, real PageView and real console action runtime, a raw-HTTP api action: the stubbed SchemaRenderer consumer mounts 1 -> 2, the stubbed InterfaceListPage consumer mounts 1 -> 2). Zone 2 item 2 measured: nothing reads the context's refreshKey (runtime reach probe: a page node carrying context.refreshKey 777 through the real SchemaRenderer and PageRenderer reaches no block's props; a control block carrying 778 in its own node was seen), so it can be dropped. Zone 2 item 3 answered: a page binds no object and the runtime's onRefresh carries none (flows and server actions may write any object), so the page's scope is the bus's documented unknown-scope value, notifyDataChanged with objectName ''. The ruled mechanism's assumption is FALSIFIED for most page blocks: a runtime census (real SchemaRenderer, real plugin registrations, counting fake dataSource, then notifyDataChanged with '') shows only object-grid, object-chart and detail-view re-read; list-view, object-kanban, object-metric, object-form (edit) and object-calendar do NOT (3 of the 4 object-bound blocks in the studio palette's data category). Page actions are raw HTTP (api absolute target, flow trigger, server action), so no onMutation fires either. Today those blocks refresh only because the page remounts; a PageView-only bus fix trades remount damage for stale data on them, and the interface branch's ListView also reads no bus. The claim's file surface says stop on breach, and the ruled text cannot be met inside it, so I stopped: no code, no PR; the worktree is clean and the pushed claim branch sits at BASE. Dispatch correction: objectui#9673 is not queued or unclaimed; it is closed (completed) by PR #10541 (c2c4372), whose pin PageView.context-9673.test.tsx expects the node context to equal exactly params plus refreshKey 0, so the eventual host change must update that pin's expected value (a planned edit, not a conflict).",
    "tests": "No code change, so no type-check, ablation or gate family ran: NOT MEASURED (reason: needs_decision stop before any edit). Measurements, all through bash /home/user/objectstack/scripts/pm/os-verify-lock.sh from the worktree root at BASE aa6be30: (1) existing pins green on BASE: pnpm exec vitest run over PageView.test.tsx, PageView.context-9673.test.tsx, page-kind-writing-end-9718.test.tsx and no-refresh-key-remount.ratchet.test.ts -> 'Test Files 4 passed (4)', 'Tests 9 passed (9)', VERDICT command-exit 0. (2) repro probe (scratch file, deleted, never committed): 'REPRO schema-renderer branch: mountsBefore=1 mountsAfterPageAction=2 authFetchCalls=1' and 'REPRO interface branch: mountsBefore=1 mountsAfterPageAction=2 authFetchCalls=1'; 'Tests 2 passed (2)', VERDICT command-exit 0. (3) bus census probe (scratch, deleted): mount reads -> reads after notifyDataChanged objectName '': object-grid 1->2, object-chart 1->2 (aggregate), detail-view 1->2 (findOne) [the lit controls]; list-view 1->1, object-kanban 1->1, object-metric 1->1, object-form edit 1->1 (findOne), object-calendar 1->1. element:number, element:repeater and the list alias issued 0 reads on mount under my probe config: NOT MEASURED, not read as absent. 'Tests 12 passed (12)', VERDICT command-exit 0. (4) reach probe (same file): 'blockPropsCarrying777=0 CONTROL_blockPropsCarrying778=2'. Worktree after deleting the probes: git status --porcelain empty; remote branch head == aa6be30.",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/10519/comments (this os-dev-report, via post-stamped.mjs on the fleet relay). The git push of the empty claim branch is a git write, not REST. Zero label writes, zero PR writes.",
    "open_questions": [
    {
    "question": "The ruled fix (onRefresh declares the change on the bus, no key bump) leaves 5 of 8 measured page blocks stale after a raw-HTTP page action, because those renderers read no invalidation bus (list-view, object-kanban, object-metric, object-form edit, object-calendar), and the interface branch's ListView reads none either. In what order does the work land? Governing text: objectui AGENTS.md section 5 commandment 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') and the data-invalidation docblock ('every mounted READER of that data refetches in place'). Both, and PR #10494's own reasoning (renderers read the bus; a host counter prop is one more relay), already fix the DIRECTION: readers read the bus. What is open is order and dependency between technical tasks, which the pm-dispatch escalation rules place in the PM's own no-escalation class.",
    "options": [
    "A — widen this card to #10494's shape: one PR makes the five renderers read the bus (useDataInvalidation nonce in each fetch effect; ListView in plugin-list, ObjectKanban, the object-metric widget in plugin-dashboard, ObjectForm's edit load in plugin-form, ObjectCalendar) and then applies the host change. Cost: 5 packages plus app-shell, ListView is a large shared surface, one ablation per renderer; no stale window.",
    "B — readers first: file the five renderer gaps (one card, or one per package, #10494 shape), this card carries a Blocked-by line and is re-dispatched when they land, with the host change exactly as ruled. Cost: this card waits; each reader card is small, parallel across seats, and independently useful (any host that already refreshes by the bus alone, e.g. record pages since objectui#2269, is served too). No stale window, and the interface branch then needs no relay.",
    "C — land the host change now and accept stale data on those blocks after raw-HTTP page actions until the reader cards land. Cost: a user-visible regression window (a page button runs, the kanban, metric or form beside it does not update), and the interface branch needs a host relay (InterfaceListPage forwarding a nonce as ListView's refreshTrigger) that becomes redundant once ListView reads the bus — the relay shape #10494 declined."
    ],
    "recommendation": "B. Long-term (leads, >= 50%): A and B end in the same architecture, one mechanism where writers declare and readers read the bus; C passes through a stale window and a throwaway relay. Real business need (measured): the studio page palette's data category is grid (a layout container), object-grid, object-form, object-metric and object-kanban (BLOCK_TYPE_META in block-types.ts), and 3 of the 4 object-bound ones do not re-read on the bus, while page actions are raw HTTP, so C's regression lands on the palette's own blocks. Anti-AI-error: once every palette data block reads the bus, an AI composing a page cannot pick a block that silently stops refreshing, and the no-refresh-key-remount ratchet can then take PageView into IN_SCOPE and close the class for this surface. No sprawl: B adds no capability and splits the work into small same-shaped changes; A concentrates five packages into one review. The host change is already designed and stays small (see summary and findings), so waiting costs little."
    }
    ],
    "out_of_scope_findings": [
    "class: b · page-embeddable data blocks do not re-read on the invalidation bus: list-view (ListView), object-kanban (ObjectKanban), object-metric (ObjectMetricBlock / ObjectMetricWidget), object-form in edit mode (ObjectForm), object-calendar (ObjectCalendar). Evidence: census probe above, delta 0 after notifyDataChanged with objectName '
    ' while object-grid, object-chart and detail-view (the lit controls) re-read once each. Contract quoted: AGENTS.md section 5 commandment 8 'invalidate the affected data (notifyDataChanged from @object-ui/react) so consumers refetch in place'; data-invalidation.ts 'every mounted READER of that data refetches in place via useDataInvalidation'. Seam: spec: notifyDataChanged / useDataInvalidation (@object-ui/react) -> renderer: ListView | ObjectKanban | ObjectMetricWidget | ObjectForm | ObjectCalendar. Filing depends on the open question: sub-issues of #10519 under A, independent card(s) with #10519 carrying Blocked-by under B. Unmeasured siblings worth the same probe: element:number, element:repeater, object-timeline, object-gallery, object-map. Dedupe words: useDataInvalidation kanban · invalidation bus list-view refetch · object-metric stale after action · notifyDataChanged ObjectForm edit reload",
    "carrier: the seat that lands the eventual host change on #10519 · noted, not filed: (i) the header of packages/app-shell/src/no-refresh-key-remount.ratchet.test.ts calls PageView's onRefresh 'an explicit user Refresh this page affordance' that legitimately remounts; false by a source reading of useConsoleActionRuntime's refresh call sites, since onRefresh is fed only by the action runtime's post-action refresh (api, flow and server-action success, undo, screen-flow completion) and PageView has no refresh control. The host change should correct that sentence and add PageView to IN_SCOPE (the regex already matches key={refreshKey}). (ii) PageView.context-9673.test.tsx expects the node context to be params plus refreshKey 0; dropping the unread refreshKey changes that expected value to params alone. (iii) Host design ready for the re-dispatch: drop key={refreshKey} on InterfaceListPage and SchemaRenderer, onRefresh calls notifyDataChanged with objectName '*', drop the counter and the context refreshKey, pins beside the PageView tests in #10494's shape (same instance; a bus-reading block re-reads exactly once; lit control; ablation restores the key)."
    ]
    }

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat decision: B, readers first · pm:dispatched → pm:blocked, Blocked-by: #10572

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. Review of the dev's needs_decision report on this card (no PR; the claim branch sits at base aa6be300b). The open question is the ORDER of technical tasks, which the pm-dispatch escalation rules place in the seat's own non-escalation class. So the seat answers it, and the maintainer's veto window applies.

    What the dev measured (⛔ not re-run by the seat)

    • The defect is real on both branches. One successful page action remounts the renderer: SchemaRenderer consumer mounts 1 → 2, and InterfaceListPage consumer 1 → 2.
    • Nothing reads the context's refreshKey. A page node carrying context.refreshKey 777 reached no block's props; a control block carrying 778 in its own node was seen. So it can be dropped.
    • Scope of the invalidation: a page binds no object, and the runtime's onRefresh carries none, so the page's event is the bus's unknown-scope notifyDataChanged({ objectName: '*' }).
    • The ruled mechanism's assumption is falsified for most page blocks. After a '*' bus event, only object-grid, object-chart and detail-view re-read. list-view, object-kanban, object-metric, object-form (edit) and object-calendar do not. Page actions are raw HTTP, so no onMutation fires either. Today those five refresh only because the page remounts.

    Decision: B (readers first)

    The five renderer gaps are filed as #10572, in PR objectui#10494's shape: each renderer's fetch effect reads the bus, with no host counter prop and no relay. This card waits on it (Blocked-by: in the body), and is re-dispatched when it lands, with the host change exactly as ruled.

    Why B and not the alternatives:

    • A (widen this card to all five renderers plus the host) concentrates five packages into one review.
    • C (host change now) opens a stale-data window on three of the studio palette's four object-bound blocks, and needs a throwaway relay for InterfaceListPage → ListView, the shape objectui#10494 declined.

    A and B end in the same architecture: writers declare, readers read. B gets there in small parallel pieces, and each is useful on its own (record pages already refresh by the bus alone).

    The host change, ready for the re-dispatch (the dev's design)

    • Drop key={refreshKey} on InterfaceListPage and SchemaRenderer, drop the counter and the context refreshKey, and have onRefresh call notifyDataChanged({ objectName: '*' }).
    • Pins beside the PageView tests in PR objectui#10494's shape: the same instance is kept, a bus-reading block re-reads exactly once, with a lit control, and the ablation restores the key.
    • Planned edits, not conflicts:
      • PageView.context-9673.test.tsx (objectui#9673, closed by PR objectui#10541) expects the node context to be params plus refreshKey: 0, and becomes params alone.
      • The header of packages/app-shell/src/no-refresh-key-remount.ratchet.test.ts calls PageView's onRefresh a user "Refresh this page" affordance. That is false by a source reading: it is fed only by the action runtime's post-action refresh. Correct it and add PageView to IN_SCOPE.
    • Dispatch correction taken: the order said objectui#9673 was queued and unclaimed. It is closed.

    State in one act: pm:dispatched → pm:blocked, the claim released (assignee cleared), and Blocked-by: #10572 at the head of the body. ⛔ No re-dispatch until #10572 closes, and then only on a re-read of this record against main.


    Generated by Claude Code

  6. 28 remaining items

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: domain:ui seat 2, session session_014mXUNuFomfj24w7s1pZzhN. The shift closed on the maintainer's instruction 「当前任务处理完,合并后就下班」 (this session's chat, 2026-09-28), so the seat does not start round 4. I cleared the assignee on the card and on PR objectui#10777, and moved the card from pm:dispatched to pm:queue. The next holder claims it afresh.

    State at release, read from GitHub and git, not from any report:

    • Draft PR objectui#10777, branch claude/issue-10519-pageview-refresh-in-place, head 42226f9ed1 (round 3, 2026-09-28T03:18Z).
      • It is 78 commits behind main 5d689c3f6e.
      • git merge-tree --write-tree against that main is clean, with 0 conflicts.
      • It has not been contract-reviewed since the round-3 FAIL 5862674975.
    • The blocker has cleared. objectui#10887 closed completed at 12:41Z through PR objectui#10973 (member 3: the kind: 'react' page author scope injects useDataInvalidation). With PR objectui#10898's two members, every reader that review 5862674975 named now reads the bus (seat 1's note 5870369205). The body's Blocked-by line moved to its history at 18:0xZ.
    • The rulings that still bind: decision B 5830213285 (「readers first」), and the seat rulings 5856239953, 5860118207 and 5862691924 (the round-4 list below).

    What the next holder owes (round 4, as seat ruling 5862691924 set it):

    1. Merge main into the branch as a merge commit. ⛔ No rebase and no force-push.

    2. Add census rows on the merged head:

      • for object-view: kanban, plus at least one of calendar, gallery, timeline, map or tree;
      • for a dashboard with an optionsFrom filter;
      • for a kind: 'react' page, because member 3 did inject useDataInvalidation.

      Every row must refresh through the bus after a page action, with none left on the remount. If the census finds a new remount-only reader, "readers first" applies again: file its reader card and set Blocked-by.

    3. Extend sentence 5 to exactly the measured green set, and fix the two nits: the 9673 line wrap, and the "Gates (head 4620ba39d2)" heading. Return a full pr_body_replacement.

    4. Run a contract review on the round-4 head (Clause-②: no per the claim), then land: the required checks green, git merge-tree against main, ACCEPT, then ready and auto-merge.

    Hot file: packages/app-shell/src/views/PageView.tsx. Before the final push, re-read the open-PR file lists for it.

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 13 · round 4 of the existing draft PR objectui#10777
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-10519-pageview-refresh-in-place (existing; head 42226f9ed1 at release)
    Worktree: objectui-issue-10519d
    Domain: domain:ui
    Seat: domain:ui#1
    File surface (seat 2's release 5876662981, which restates seat ruling 5862691924's round-4 list):

    1. Merge main into the branch as a merge commit. ⛔ No rebase, no force-push.

    2. Census rows on the merged head, each refreshing through the bus after a page action, none left on the remount:

      • object-view kanban plus at least one of calendar, gallery, timeline, map or tree;
      • a dashboard with an optionsFrom filter;
      • a kind: 'react' page (objectui#10887 member 3 injected useDataInvalidation).

      If the census finds a new remount-only reader, "readers first" (decision B 5830213285) applies again: stop and report. Filing the reader card and setting Blocked-by is the seat's write.

    3. Sentence 5 extended to exactly the measured green set, plus the two nits: the 9673 line wrap, and the "Gates (head 4620ba39d2)" heading. Return a full pr_body_replacement; the seat patches the body.

    4. Gates on the round-4 head. The seat then runs a contract review and lands.

    Hot file: packages/app-shell/src/views/PageView.tsx. Re-read the open-PR file lists before the final push.
    Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12692 at 81137630)
    Clause-②: no
    Why no: as both earlier claims declared (5829882400, 5855640194), this is a host's refresh path, and no declared surface moves. The round-4 items are a main merge, census rows, pins and PR-body text.
    Thread-read: 5876662981
    Serial constraints cleared: read 2026-09-28T20:09Z at objectui origin/main f6fb83f0c. Open PRs objectui#11006, #10930, #10278 and #5400: none touches PageView.tsx or packages/app-shell/src/views/. PR objectui#10990 has merged.

    domain:ui seat #1 · claim · 2026-09-28T20:09Z


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10519,
    "status": "done",
    "branch": "claude/issue-10519-pageview-refresh-in-place",
    "pr": "#10777",
    "session": "session_01DuWo5bdP9SdVebamn99GGk — mode:subagent, so this is the parent PM seat's id (domain:ui#1, claim 5877617401)",
    "premise_still_valid": true,
    "summary": "Round 4 of draft PR objectui#10777. The worktree started on the remote head 42226f9 (verified equal). origin/main f6fb83f (82 commits past 1c5ee33, PR objectui#10898 and PR objectui#10973 among them) merged clean as the merge commit e021bfa, whose tree equals the measured git merge-tree tree 2808afb79b. The six app-shell PageView test files passed on that merged tree before anything was added (16/16). The census then ran the rows ruling 5862691924 names, on the merged head, through the real PageView and one real api page action each: object-view drawn as a kanban, calendar, gallery and timeline, a dashboard globalFilters select with optionsFrom (find and dataset reads), and a kind:'react' page whose effect names useDataInvalidation. Every row re-reads its object once (1 to 2) with no remount: the witness on the block's own registry entry reads 1 to 1 with +0 unmounts, the page stand-in keeps its instance, and state a remount resets survives. No new remount-only reader was found, so readers-first does not stop this round. Red legs: A1 (main's pre-fix PageView) remounts every row; A2 (the host declares nothing) leaves every row at 1 to 1; R3 (each reader on its round-3 code path) leaves its rows at 1 to 1, and the round-3 react scope throws ReferenceError. Four rows are pinned in PageView.refreshInPlace-10519 (commit beac974): the kanban, gallery and calendar object-views and the dashboard filter's find read. Commit fecce55 extends the changeset list to exactly the rows measured green and adds one measured sentence on kind:'react' pages. It rewraps the 9673 entry, whose words are unchanged. It also changes 'does' to 'did' in the pending 10887-react-page entry, which this PR makes false in the same release; that file is outside the claim's listed surface and is declared as such. The PR assignee is set back to os-steve. CI on fecce55 is complete: 43 runs, 40 success, 3 skipped. pr_body_replacement is the full body, not PATCHed. PM claim falsified: Bundle Analysis is not red on main; it is success on f6fb83f, after PR objectui#11018 re-pinned the ceiling.",
    "tests": "All heavy runs went through bash /home/user/objectstack/scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-10519-r4, and each verdict comes from its VERDICT line. (1) At the merged head e021bfa, before any edit, six test files: PageView.refreshInPlace-10519, no-refresh-key-remount.ratchet, PageView.context-9673, PageView.test, page-kind-writing-end-9718 and PageView.editInStudioNeedsAuthoring-10899. Result: 'Test Files 6 passed (6)', 'Tests 16 passed (16)', VERDICT command-exit 0. (2) The census probe, packages/app-shell/src/views/tests/census-10519-r4.probe.test.tsx, was never committed; it is archived as issue-10519d/census-10519-r4.probe.test.tsx.archived (md5 af5ede3a47187d8a201e3df4548b6499) and deleted from the tree. At fecce55: 'Tests 8 passed (8)'. Rows: object-view kanban reads 1 → 2 · mounts 1 → 1 · unmounts +0 · stand-in 1 → 1 · same inner DOM node true | object-view calendar 1 → 2 · 1 → 1 · +0 · 1 → 1 · region node false (the loading placeholder while it re-reads), navigated month October 2026 kept | object-view gallery 1 → 2 · 1 → 1 · +0 · 1 → 1 · true | object-view timeline 1 → 2 · 1 → 1 · +0 · 1 → 1 · true | react page (names the nonce) 1 → 2 · author mounts 1 → 1 · app entry 1 → 1 · +0 · counter 1 kept · same button node | react page (adapter-only effect, control) 1 → 1 · no remount | dashboard optionsFrom find read 1 → 2 · 1 → 1 · +0 · 1 → 1 · retail kept | dashboard optionsFrom dataset read, the same. (3) Legs, all at beac974, over the probe's first 7 cases and the pin's 7. Each restore was proven by blob equal to HEAD and git diff HEAD empty. A1: PageView.tsx replaced by main's blob 1ace6962da, with 2 key={refreshKey} sites on disk. Every row reads mounts 1 → 2, unmounts +1, stand-in 1 → 2, node replaced; the month resets, the filter reads 'Industry: All', and the react counter reads 0. Pin 'Tests 6 failed | 8 passed (14)': the six in-place cases fail on (a), and the lit control stays green. A2: ablation-replace, anchor " notifyDataChanged({ objectName: '' });" hits 1 → 0, blob 979bb11eb7 → 7bb3888fd2. Every row reads 1 → 1 with no remount. 'Tests 6 failed | 8 passed (14)', all on (b). R3 view: ablation-replace deletes the \n invalidationNonce, entry (1 → 0), blob db64424727 → 90e198e990. The object-view rows read 1 → 1. 'Tests 3 failed | 11 passed (14)': kanban, gallery and calendar fail on (b). R3 dash: ', dataSource, invalidationNonce]);' becomes ', dataSource]);' (1 → 0), blob 9d93a32b56 → b636341b96. The dashboard rows read 1 → 1. 'Tests 1 failed | 13 passed (14)'. R3 react: react-page.tsx replaced by its 42226f9 blob f70b8f4251. The react row fails with 'ReferenceError: useDataInvalidation is not defined'. R3' react: the hook becomes a function that always returns 0 (1 → 0), blob ab712dface → ade9a50713. The react row reads 1 → 1. No dist preflight applies: the relative import and the root vitest alias resolve every mutated file to src. (4) At the final head fecce55, in one lock hold: census 8/8; the six PageView files 'Test Files 6 passed (6)', 'Tests 20 passed (20)'; 27 reader pins 'Test Files 27 passed (27)', 'Tests 171 passed (171)'; VERDICT command-exit 0. Also i18n residue-namespaces-3546, which reads PageView.tsx: 43/43, VERDICT 0. (5) turbo run build --concurrency=2 --filter=@object-ui/app-shell^... reports 'Tasks: 28 successful, 28 total'. Then pnpm --filter @object-ui/app-shell type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json') gives VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles names the pin once. (6) pnpm exec vitest run scripts/ --maxWorkers=2: 'Test Files 177 passed | 2 skipped (179)', 'Tests 5350 passed | 2 skipped (5352)', VERDICT 0. (7) These gates each exit 0 with an OK line: check-changeset-presence, no-major, fixed, changeset:check, overwrite (report-only: 1 added, 2 modified, bumps unchanged), changeset-claims, pending-changeset-literals, control-bytes, new-line-citations ('0 new citation(s)'), test-path-roots, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, phantom-deps, unused-deps, shell-escape-residue and unreferenced-sources. check-governed-queue-guard --test over 7 paths reports NOT GOVERNED. (8) ESLint, narrowed: --no-inline-config --format json over the 4 touched source and test files reports 4 files, 0 errors, 29 warnings, all no-explicit-any. The pin went from 18 to 19; PageView.tsx has 5, the same as main. eslint.config.js has no projectService or parserOptions, so the check is not type-aware. CI Lint is success. (9) Eager closure: a console vite build at the head, and again with main's PageView.tsx blob restored by hash, both read '3105.0 KB gzipped across 330 of 2446 chunks (budget: 3149.4 KB, headroom: 44.4 KB)', with equal gzip byte totals; PageView is a lazy chunk. CI Bundle Analysis is success on the head and on main f6fb83f. (10) NOT MEASURED: the full app-shell suite locally. It has 869 test files; my own run was stopped after 20 minutes under the lock, killed by its PIDs. CI Test shards 1 to 8 are success on fecce55. (11) CI on fecce55 is complete: 43 runs, 40 success, 3 skipped, Spec Main Shape Gate and Bundle Analysis included.",
    "mcp_calls": "0",
    "api_writes": "2 — (1) node /home/user/objectstack/scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 10777 --assign os-steve, exit 0: one relay dispatch (fleet-write request fw-20260928T201420Z-a8eedd, run 36477776712, conclusion success) executing POST /repos//issues/10777/assignees; read back MATCHES (labels tests, package: app-shell; assignee os-steve). (2) POST /repos//issues/10519/comments: this os-dev-report, via post-stamped.mjs on the fleet relay. git push x2 (42226f9..e021bfa, e021bfa..fecce55, both fast-forward) are git writes, not REST. REST GETs only otherwise (issue, 19 comments, PR, PR comments, reviews, PR files of the open PRs, check-runs, one check-run's annotations). No PATCH, no label write, no MCP call.",
    "open_questions": [],
    "review_5862674975_dispositions": [
    "Item 1(a), object-view non-grid views: fixed upstream by PR objectui#10898 member 1 (9f0c84a), in this branch through the merge e021bfa. The census rows kanban, calendar, gallery and timeline are green through a real page action. Kanban, gallery and calendar are pinned in this PR.",
    "Item 1(b), a dashboard's optionsFrom filter options: fixed upstream by PR objectui#10898 member 2. The census rows for the find and dataset reads are green, and the selected value is kept. The find read is pinned in this PR.",
    "Item 1(c), kind:'react' pages: the seat ruled, and PR objectui#10973 (deca847) injects useDataInvalidation. The census row (the author effect names the nonce) is green. The adapter-only control (1 to 1) is measured, and the changeset states it.",
    "Nit, the 9673 line wrap: fixed on the head (fecce55). The paragraph is rewrapped to the file's width; the whitespace-normalised text is identical and the frontmatter md5 4e8ce55021fe3b14329c8b4cb3f9f354 is unchanged.",
    "Changeset, 'a patch that silently removes the only post-action refresh of (a) and (b)': moot. The readers landed, and sentence 5's list is extended with exactly the rows measured green. No disclosure sentence stands in for a reader.",
    "PR body, 'the H1 narrowing states the premise gap rather than closing it': fixed in pr_body_replacement. The H1 closing sentence is extended, and a new H1, round 4 section carries the census, the legs and the reader pins.",
    "Nit, the heading 'Gates (head 4620ba3)' also held later rounds' bullets: fixed in pr_body_replacement. It now reads '## Gates', with Round 1 to Round 4 labels.",
    "Verdict item 3 (merge main; census rows; extend and trim sentence 5): done.",
    "Boundary, 'the PR is behind': fixed by the merge. Coordination with PR objectui#10884 on ObjectView.tsx: moot, since it merged (35d68c4) and this PR does not touch ObjectView.tsx.",
    "Other pending changesets: re-read at the head. 10887-object-view and 10887-dashboard ('PageView is about to stop doing that') come true on landing. 10887-react-page's '(as PageView does after a page action)' became false with this PR and is corrected to 'did'."
    ],
    "census": [
    "object-view defaultViewType kanban | reads 1 → 2 | witness 1 → 1, +0 | stand-in 1 → 1 | card node same | pinned",
    "object-view calendar (named view calendar.startDateField) | 1 → 2 | 1 → 1, +0 | 1 → 1 | month kept; the region node swaps for the loading placeholder during the re-read | pinned",
    "object-view defaultViewType gallery | 1 → 2 | 1 → 1, +0 | 1 → 1 | card node same | pinned",
    "object-view timeline (named view timeline.startDateField) | 1 → 2 | 1 → 1, +0 | 1 → 1 | item node same | probe only",
    "dashboard globalFilters select optionsFrom, find read | 1 → 2 | 1 → 1, +0 | 1 → 1 | selected value kept, trigger node same | pinned",
    "dashboard globalFilters select optionsFrom, dataset read | 1 → 2 | 1 → 1, +0 | 1 → 1 | as above | probe only",
    "kind:'react' page, effect names useDataInvalidation('project'), action is a Block of type action:button in the page | 1 → 2 | app entry 1 → 1, +0; author mount effect 1 → 1 | n/a | counter kept, button node same | probe only",
    "control: kind:'react' page, read keyed on [adapter] alone | 1 → 1 | 1 → 1, +0 | n/a | counter kept | probe only"
    ],
    "remount_only_readers_found": "none",
    "deviations": [
    ".changeset/10887-react-page-data-invalidation.md is edited ('does' to 'did'), and the claim's listed file surface does not name it. I applied the role file's in-place rule for a bounded fix: the same class as the 9673 planned edit (a pending entry this branch makes false in the same release), a mechanical plain replacement with the frontmatter md5 3d9e2a1e59bc11924bb99ffd9eea81e3 unchanged, no other claim on the file, and the same changeset gate family. The PR body names it. The seat may drop it and file it instead.",
    "Sentence 5 gains one sentence beyond the list, on kind:'react' pages: 'A kind: 'react' page is no longer remounted either: its own read re-runs in place when its effect names the useDataInvalidation nonce the page scope injects (objectui#10887), and a read keyed on useAdapter alone is not re-run by a page action.' Both halves are measured (1 to 2, and the adapter-only control 1 to 1). A react page is not an embedded block, so it does not fit the block list.",
    "Four census rows are committed as a new describe block in the PR's own pin; the timeline, dataset-read and react-page rows stay probe-only. app-shell declares neither plugin-timeline nor react-runtime, and a react page's runtime is a dynamic import a committed test would race.",
    "The full app-shell suite was not run locally. My own run was stopped by its PIDs after 20 minutes to free the shared lock. CI's 8 test shards are green on the head.",
    "post-stamped.mjs was run from a git archive of objectstack origin/main 9449512a (scripts/ and package.json, extracted into issue-10519d/os-main; self-test 629 cases pass), because the shared checkout's copy differs. The fetch into a private ref in /home/user/objectstack also advanced that checkout's remote-tracking origin/main (81137630..9449512a); nothing else there was touched, and the private ref is deleted.",
    "Cleanup: node_modules was removed, then git worktree remove /home/user/objectui-issue-10519d ran without --force and exited 0 on a clean tree whose HEAD equals the remote fecce55. The private refs refs/issue-10519d/
    were deleted."
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed (observation, already recorded as PR objectui#10973's note (c)): ObjectView's non-grid fetch sets loading on every run, a bus re-read included. ObjectCalendar swaps its grid for 'Loading calendar…' while loading is set, so after a page action the calendar region is a new DOM node, while ObjectCalendar's month and view mode (its own state) are kept. It is in the PR body's Acceptance notes. Dedupe words: calendar loading flash re-read · ObjectView non-grid loading · object-view calendar placeholder"
    ],
    "pr_body_replacement": "Fixes #10519\nClause-②: no\n\nDispatched by the domain:ui seat 2 PM, session session_014mXUNuFomfj24w7s1pZzhN (mode:subagent, so that id is also this run's: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN), from claim comment 5855640194 on objectui#10519, executing the unblock 5854561985 and seat decision B 5830213285. Branch claude/issue-10519-pageview-refresh-in-place, cut from origin/main b5b6f37531 (BASE); round-1 head 4620ba39d2. Round 2 (after the contract review 5856227410, FAIL; the ruling 5856239953, readers first; the Blocked-by amendment 5857953021) merged origin/main 195052fff6 as 25c9aa6d3a and re-ran the census there. It stopped at the census: see H1, round 2. Round 3 (after the seat ruling 5860118207, readers first, and objectui#10853 landing through PR objectui#10873 as de1b879a6) merged origin/main 1c5ee334a2 as c5b63fb514, re-ran the census there with every row green (H1, round 3), and carried review items 2 to 4 and the ratchet titles; its head was 42226f9ed1. Round 4 (after the contract review 5862674975, FAIL; the ruling 5862691924, readers first; objectui#10887 landing through PR objectui#10898 as 9f0c84a44 and PR objectui#10973 as deca847a8; seat 2's release 5876662981; and the domain:ui seat 1 claim 5877617401, session session_01DuWo5bdP9SdVebamn99GGk, https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk) merged origin/main f6fb83f0c5 as e021bfaf24, measured the rows that ruling names on the merged head through a real page action, every one green (H1, round 4), pinned four of them in this PR's pin, and carried the changeset and body items. Head fecce55c33.\n\n## What changed\n\n- packages/app-shell/src/views/PageView.tsx: the refreshKey counter is gone, together with the key on both render branches (InterfaceListPage, ADR-0047 interface mode, and SchemaRenderer, the page). The console action runtime's onRefresh now points at a module-level declarePageDataChanged, which is notifyDataChanged({ objectName: '*' }) from @object-ui/react: a page binds no object and the runtime's refresh carries none, so the bus's documented unknown-scope value is the page's scope. The node context handed to SchemaRenderer is { params } alone. useState is no longer imported. No prop, export or schema key moves.\n- packages/app-shell/src/views/__tests__/PageView.context-9673.test.tsx: the expected node context is { params } (it was params plus refreshKey: 0); the header sentence says why.\n- packages/app-shell/src/no-refresh-key-remount.ratchet.test.ts: the header no longer calls PageView.onRefresh a user "Refresh this page" affordance. By a source reading of useConsoleActionRuntime, onRefresh is fed only by the runtime's post-action refresh (api, flow and server-action success, undo, screen-flow completion), and PageView has no refresh control. packages/app-shell/src/views/PageView joins IN_SCOPE. Every other assertion is kept. Its two it titles now name the PageView page host beside the record-detail surfaces (round 3, titles only).\n- New pin packages/app-shell/src/views/__tests__/PageView.refreshInPlace-10519.test.tsx (below). Round 4 adds a describe block for the objectui#10887 readers (H1, round 4) and a second object (account) to its fixture; the round-1 cases are unchanged.\n- .changeset/10519-pageview-refresh-in-place.md: '@object-ui/app-shell': patch. Round 3: the refresh follows a page-level action "that asks for a refresh"; PageView, not the runtime, declares the change; the block list is exactly the set the round-3 census measured green. Round 4: the list adds a dashboard's optionsFrom filter options and an object-view drawn as a kanban, calendar, gallery or timeline (the rows H1, round 4 measured green), the dashboard filter's kept value is said beside the form's, and a sentence says what a page action now does to a kind: 'react' page: its own read re-runs in place when its effect names the injected useDataInvalidation nonce, and a read keyed on useAdapter alone is not re-run.\n- .changeset/9673-pageview-drop-context-spread.md (pending, not in the app-shell CHANGELOG): "The node's context is now exactly { params, refreshKey }, built from the route and the refresh counter, and the as any cast at that spot is gone." becomes "The node's context is now built from the route alone, and the as any cast at that spot is gone." A plain replacement, not a dated note: the edit can only reach a release together with this PR's code (a release that consumed the entry first would ship the old sentence, true for that release, and turn this edit into a modify/delete conflict), so the replacement is the entry's one release-scoped reading. Frontmatter byte-identical (md5 of lines 1 to 3 4e8ce55021fe3b14329c8b4cb3f9f354 before and after); check-changeset-overwrite reads @object-ui/app-shell: patch at base and now. Round 4 rewraps the replaced sentence to the file's width: the words are unchanged (the whitespace-normalised text is identical) and the frontmatter md5 is the same.\n- .changeset/10887-react-page-data-invalidation.md (pending, from PR objectui#10973, not in the components CHANGELOG): "(as PageView does after a page action)" becomes "(as PageView did after a page action)". This branch stops that remount, and the two entries ship in the same release. A plain replacement, on the 9673 entry's argument. Frontmatter byte-identical (md5 of lines 1 to 3 3d9e2a1e59bc11924bb99ffd9eea81e3 before and after); check-changeset-overwrite reads @object-ui/components: minor at base and now. This file is outside the claim's listed surface; see Acceptance notes.\n\n## H1 — premise: the census rows re-read in place on origin/main\n\nRuntime census on BASE b5b6f37531, through a scratch probe (deleted, never committed): the real SchemaRenderer, each plugin's real registration, one counting fake data source per row; reads on mount, then notifyDataChanged({ objectName: '*' }), then reads again. A bare useDataInvalidation reader beside each block is the positive control, and its nonce moved 0 → 1 in every row. The probe lived under app-shell because the root vitest alias map resolves every @object-ui/plugin-* specifier to source there; committed, it would trip check:phantom-deps (the objectui#10572 PR made the same call).\n\n| block | reads on mount → after the bus event | re-reads in place |\n|:--|:--|:--|\n| object-grid | 1 → 2 | yes |\n| object-chart (aggregate) | 1 → 2 | yes |\n| detail-view (findOne) | 1 → 2 | yes |\n| list-view | 1 → 2 | yes |\n| object-kanban | 1 → 2 | yes |\n| object-metric (aggregate) | 1 → 2 | yes |\n| object-form, default arm (findOne) | 1 → 2 | yes |\n| object-form formType: drawer | 1 → 2 | yes |\n| object-form formType: modal | 1 → 2 | yes |\n| object-form formType: split | 1 → 2 | yes |\n| object-form formType: tabbed | 1 → 2 | yes |\n| object-form formType: wizard | 1 → 2 | yes |\n| object-calendar | 1 → 2 | yes |\n| object-gallery | 1 → 2 | yes |\n| object-timeline | 1 → 2 | yes |\n| object-gantt | 1 → 2 | yes |\n| element:number (aggregate) | 1 → 2 | yes |\n| element:repeater | 1 → 2 | yes |\n| object-map | not probed here: its committed pin ObjectMap.invalidationRefetch-10623 mocks react-map-gl/maplibre, which does not resolve from app-shell; that pin ran green on BASE in the same lock run (3 cases) | yes |\n| list (bare key) | n/a | not a data block: @object-ui/components owns the bare key as the display list, and plugin-list registers ListViewRenderer as view:list with skipFallback: true so that it never takes that key; view:list is the list-view row's renderer. |\n\nThe studio palette's data category (BLOCK_TYPE_META) is grid, object-grid, object-form, object-metric, object-kanban; every object-bound entry is in the table. Every block in this table, the six object-form arms included, re-reads exactly once after the bus event on BASE. What this PR claims covers the census rows only, not every block a stored page can hold: this table; the rows of the round-2 table (object-tree, object-pivot, object-data-table, report / spec-report over a dataset, record:line_items with an authored parent, a dashboard dataset table widget), whose readers landed with objectui#10778 and objectui#10814; and the rows round 3 adds, object-master-detail-form's edit-mode detail lines and element:record_picker's options, whose readers landed with objectui#10853. On the merged head c5b63fb514 every one of them re-reads once in place with no remount (H1, round 3). Round 4 adds the rows the ruling 5862691924 names, whose readers landed with objectui#10887: an object-view drawn as a kanban, calendar, gallery or timeline, a dashboard's optionsFrom filter options, and a kind: 'react' page whose effect names the injected useDataInvalidation nonce. Each re-reads once in place after a real page action on the merged head (H1, round 4).\n\n## H1, round 2 — census on the merged head 25c9aa6d3a\n\nThe round-1 probe, re-run unchanged on its 18 rows, plus the rows the ruling names and one for the dashboard block, whose dataset widgets read on a page since objectui#10815. Scratch probes again, never committed, archived in the seat scratchpad (issue-10519/census-10519-r2.probe.test.tsx.archived, md5 ba70dc05bf7a266d6a7535c9c7990b2e; issue-10519/census-10519-r2-md.probe.test.tsx.archived, md5 2d830df32bf6975b2be2edc5cb2050ef). New this round, a remount witness: the row's own registry entry (ComponentRegistry.get, which SchemaRenderer resolves the node through) is wrapped in a counter whose dependency-free mount effect runs once per instance and whose cleanup runs once per unmount. Witness control: object-metric under a host re-keyed on the bus nonce, the shape of the removed key={refreshKey}, reads 1 → 2 like an in-place re-read, and the witness reads mounts 1 → 2, unmounts +1. So the read count alone cannot tell the two apart, and the witness can. Run under the verify lock, Tests 29 passed (29), VERDICT command-exit 0 (26 probe cases, the three ObjectMap.invalidationRefetch-10623 cases).\n\n| block | reads on mount → after the bus event | mounts at the event → after · unmounts | re-reads in place, no remount |\n|:--|:--|:--|:--|\n| the 18 round-1 rows (object-grid, object-chart, detail-view, list-view, object-kanban, object-metric, object-form default / drawer / modal / split / tabbed / wizard, object-calendar, object-gallery, object-timeline, object-gantt, element:number, element:repeater) | 1 → 2 each | 1 → 1 · +0 each | yes |\n| object-tree | 1 → 2 | 1 → 1 · +0 | yes |\n| object-pivot | 1 → 2 | 1 → 1 · +0 | yes |\n| object-data-table | 1 → 2 | 1 → 1 · +0 | yes |\n| report over a dataset (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| spec-report over a dataset (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| record:line_items with an authored parentId | 1 → 2 | 1 → 1 · +0 | yes |\n| dashboard, a dataset table widget (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| object-map | its committed pin ObjectMap.invalidationRefetch-10623, same run: one re-read after '*', the map node unchanged (3 cases) | n/a | yes |\n| witness control: object-metric under a host re-keyed on the nonce | 1 → 2 | 1 → 2 · +1 | by remount only |\n\nTwo blocks a stored page can hold do not re-read. This is the stop condition 5854561985, and round 2 stopped here.\n\n| block, as a stored page holds it | reads on mount → after the bus event | mounts · unmounts | re-reads in place |\n|:--|:--|:--|:--|\n| object-master-detail-form, mode: 'edit', authored recordId: the header (findOne of the parent) | 1 → 2 | 1 → 1 · +0 | yes |\n| the same block: its detail lines (find of the child object) | 2 → 2 | 1 → 1 · +0 | no |\n| the same block under a host re-keyed on the nonce: its detail lines | 2 → 4 | 1 → 2 | by remount only |\n| element:record_picker: its options (find of its object) | 1 → 1 | 1 → 1 · +0 | no |\n\n- A stored page can hold both. Both are ComponentPropsMap keys in @objectstack/spec 17.4.0, the installed version. PageSchema.safeParse accepts a page holding object-master-detail-form with mode: 'edit', recordId and details, and ComponentPropsMap['object-master-detail-form'] parses those properties. objectui registers both: object-master-detail-form in plugin-form, which the console's register-plugins.ts imports eagerly, and record_picker in components. Both read through the page's adapter (SchemaRendererContext, useAdapter()).\n- Source reading (context, not the measurement). MasterDetailForm's edit-mode children effect keys on isEdit, dataSource, schema.recordId and resolvedEntries. The picker's option effect keys on adapter, object, filterKey, sortKey and limit. Neither names a bus nonce, so after a page action nothing re-runs them, and before this PR the remount did.\n- Recorded, not gating. object-form with subforms routes to the same MasterDetailForm and measured the same (lines 2 → 2). But ComponentPropsMap['object-form'] refuses subforms by name (unrecognized_keys), so a spec-valid page does not hold that route.\n- Resolved in round 3. objectui#10853 (PR objectui#10873, de1b879a6) gave both blocks a bus reader, and round 3 measures them green below. Review items 2 to 4 and the ratchet it titles are on the branch.\n\n## H1, round 3 — census on the merged head c5b63fb514\n\nThe round-2 probe, re-run unchanged on its rows, plus element:record_picker's options in the same harness and a new section for object-master-detail-form in edit mode with an authored recordId (the po / po_line shape objectui#10853's own pin uses, with the real line grid). Real SchemaRenderer, real registrations, one counting fake data source per row, one notifyDataChanged({ objectName: '*' }), the same remount witness and its control. Scratch probe, never committed, archived in the seat scratchpad as issue-10519/census-10519-r3.probe.test.tsx.archived (md5 b1d116c257b12b3e35928dc35ddb113b), rows in issue-10519/census-rows-r3-head.txt. Run under the verify lock with ObjectMap.invalidationRefetch-10623: Test Files 2 passed (2), Tests 33 passed (33), VERDICT command-exit 0 (30 probe cases, 3 map cases). The control nonce read 1 in every row.\n\n| block | reads on mount → after the bus event | mounts at the event → after · unmounts | re-reads in place, no remount |\n|:--|:--|:--|:--|\n| object-grid | 1 → 2 | 1 → 1 · +0 | yes |\n| object-chart (aggregate) | 1 → 2 | 1 → 1 · +0 | yes |\n| detail-view (findOne) | 1 → 2 | 1 → 1 · +0 | yes |\n| list-view | 1 → 2 | 1 → 1 · +0 | yes |\n| object-kanban | 1 → 2 | 1 → 1 · +0 | yes |\n| object-metric (aggregate) | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form, default arm (findOne) | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form formType: drawer | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form formType: modal | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form formType: split | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form formType: tabbed | 1 → 2 | 1 → 1 · +0 | yes |\n| object-form formType: wizard | 1 → 2 | 1 → 1 · +0 | yes |\n| object-calendar | 1 → 2 | 1 → 1 · +0 | yes |\n| object-gallery | 1 → 2 | 1 → 1 · +0 | yes |\n| object-timeline | 1 → 2 | 1 → 1 · +0 | yes |\n| object-gantt | 1 → 2 | 1 → 1 · +0 | yes |\n| element:number (aggregate) | 1 → 2 | 1 → 1 · +0 | yes |\n| element:repeater | 1 → 2 | 1 → 1 · +0 | yes |\n| object-tree | 1 → 2 | 1 → 1 · +0 | yes |\n| object-pivot | 1 → 2 | 1 → 1 · +0 | yes |\n| object-data-table | 1 → 2 | 1 → 1 · +0 | yes |\n| report over a dataset (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| spec-report over a dataset (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| record:line_items with an authored parentId | 1 → 2 | 1 → 1 · +0 | yes |\n| dashboard, a dataset table widget (queryDataset) | 1 → 2 | 1 → 1 · +0 | yes |\n| element:record_picker: its options (find of its object) | 1 → 2 | 1 → 1 · +0 | yes |\n| object-master-detail-form, mode: 'edit', authored recordId: its detail lines (find of the child object) | 2 → 3 | 1 → 1 · +0; the first line input is the same DOM node | yes |\n| the same block: its header (findOne of the parent) | 1 → 2 | the block node of the row above | yes |\n| object-map | its committed pin ObjectMap.invalidationRefetch-10623, same run: one re-read after '*', the map node unchanged (3 cases) | n/a | yes |\n| witness control: object-metric under a host re-keyed on the nonce | 1 → 2 | 1 → 2 · +1 | by remount only |\n\nThe detail lines read twice on mount, as objectui#10853 measured at its BASE (the load effect runs again once the resolved details are set); the bus event adds exactly one read.\n\nUnsaved lines are held, then re-read once after a revert or a save (objectui#10853's rule, measured through the same block, one bus event each):\n\n| case | line reads at the event → while dirty → after | mounts · unmounts | lines on screen while held |\n|:--|:--|:--|:--|\n| type into the first line, '*', then type the stored value back (revert) | 2 → 2 → 3 | 1 → 1 · +0 | [\"first edited\"] |\n| type into the first line, '*', then the form's save (batchTransaction once) | 2 → 2 → 3 | 1 → 1 · +0 | n/a |\n\nNo block a stored page can hold, among the census rows, still refreshes only through the remount. The stop condition 5854561985 does not hold this round.\n\n## H1, round 4 — census on the merged head, through a real page action\n\nThe ruling 5862691924 names three rows: object-view (kanban, plus at least one of calendar, gallery, timeline, map or tree), a dashboard with an optionsFrom filter, and a kind: 'react' page, since objectui#10887 member 3 injected useDataInvalidation. This round measures them through the real PageView and one real page action each, not through a bare bus event. Each row is a stored page in the spec shape (regions, blocks as { type, properties }) holding the page's api action (target: /api/v1/environments, run through the console runtime's apiHandler, so no onMutation fires), a stand-in with an instance id, and the block. The probe counts the block's reads of the object it queries on a counting fake data source. It wraps the block's own registry entry (ComponentRegistry.get, which SchemaRenderer resolves the node through) in a mount and unmount witness, and it reads state a remount resets. Scratch probe, never committed: census-10519-r4.probe.test.tsx, archived in the round-4 scratchpad (md5 af5ede3a47187d8a201e3df4548b6499). At head fecce55c33, under the verify lock: Tests 8 passed (8), VERDICT command-exit 0. The probe writes each row to a file; it asserts nothing about the counts.\n\n| row | reads of its object, at the action → after | witness: mounts · unmounts | page stand-in | state a remount resets | pinned in this PR |\n|:--|:--|:--|:--|:--|:--|\n| object-view, defaultViewType: 'kanban' | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the Deal 1 card is the same node | yes |\n| object-view, calendar (a named view with calendar.startDateField) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the navigated month (October 2026) is kept | yes |\n| object-view, defaultViewType: 'gallery' | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the Deal 1 card is the same node | yes |\n| object-view, timeline (a named view with timeline.startDateField) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the Deal 1 item is the same node | no, probe only |\n| dashboard, a globalFilters select with optionsFrom (the find fallback read) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the selected retail is kept; the trigger is the same node | yes |\n| the same filter, the dataset read (queryDataset) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | as above | no, probe only |\n| kind: 'react' page whose effect names useDataInvalidation('project'); the action is a Block of type action:button inside the page | 1 → 2 | the page's app entry 1 → 1 · +0; the author's own mount effect 1 → 1 | n/a (the page is the block) | the counter clicked before the action still reads 1; the button is the same node | no, probe only |\n| control: the same react page, its read keyed on [adapter] alone | 1 → 1 | 1 → 1 · +0; author mounts 1 → 1 | n/a | the counter is kept | no |\n\n- No row re-reads only through the remount. No new remount-only reader was found, so "readers first" does not stop this round.\n- The remount key. PageView carries no key on either branch (the no-refresh-key-remount ratchet holds the file), and the witness saw no remount at or below each block's registry entry.\n- The calendar row. Its region node is not the same node after the action. While the view re-reads, ObjectView hands the calendar loading, and ObjectCalendar shows its loading placeholder in place of the grid, as PR objectui#10973's note (c) records. ObjectCalendar itself is not remounted: the witness reads 1 → 1, and the month it holds in its own state is kept. In the legs where nothing re-reads (A2, R3 below), the region node is the same, so the swap follows the re-read.\n- What is pinned. The round-4 describe block of PageView.refreshInPlace-10519 holds four rows: the kanban, gallery and calendar object-views, and the dashboard filter over the find read. Each asserts both halves: (a) the stand-in's instance id, plus the card node, the navigated month, or the filter's trigger node and selected value; (b) exactly one re-read of the block's object. Timeline, the dataset read and the react page stay probe-only. @object-ui/app-shell declares neither @object-ui/plugin-timeline nor @object-ui/react-runtime, and neither does the workspace root, which is what check:phantom-deps asks of a test import. A react page also loads its runtime with a dynamic import(), which a committed test would race inside a bounded wait (AGENTS.md, flaky-test discipline). Their readers' own pins fail on the round-3 code path: ObjectView.busReread-10887 covers the timeline, DashboardFilterBar.busReread-10887 covers both reads, and react-page-invalidation covers the react page scope (its base leg is a ReferenceError, per PR objectui#10973).\n\nLegs, each on committed state (beac974876; the commit after it changes changesets only), with the probe's first seven cases (the adapter-only control came after) and the pin. PageView.tsx and the three readers resolve to src (the relative import, and the root vitest alias for @object-ui/plugin-view, @object-ui/plugin-dashboard and @object-ui/components), so no dist/ preflight applies. Every restore is by blob hash against HEAD, with git diff HEAD empty.\n\n| leg | mutation (on-disk proof) | probe | pin |\n|:--|:--|:--|:--|\n| A1, the pre-fix host | PageView.tsx = main's blob 1ace6962da (HEAD blob 979bb11eb7; two key={refreshKey} sites) | every row: mounts 1 → 2, unmounts +1, stand-in 1 → 2, the node replaced; the month reset to September 2026, the filter back to Industry: All, the react counter back to 0 and the author's mount effect 1 → 2 | Tests 6 failed \\| 8 passed (14) over probe and pin: all six in-place cases red on (a); the lit control green |\n| A2, the host declares nothing | ablation-replace: anchor notifyDataChanged({ objectName: '*' }); 1 → 0, blob 979bb11eb7 → 7bb3888fd2 | every row: reads 1 → 1, no remount | Tests 6 failed \\| 8 passed (14): the same six red on (b) (metric, list, kanban, gallery, calendar, dashboard); the lit control green |\n| R3, ObjectView on the round-3 path | ablation-replace: the invalidationNonce entry deleted from the non-grid fetch's dependency list (1 → 0), blob db64424727 → 90e198e990 | the four object-view rows: reads 1 → 1, no remount; the others unchanged | Tests 3 failed \\| 11 passed (14): kanban, gallery and calendar red on (b) |\n| R3, SelectFilter on the round-3 path | ablation-replace: , dataSource, invalidationNonce]); → , dataSource]); (1 → 0), blob 9d93a32b56 → b636341b96 | both dashboard rows: reads 1 → 1, the value kept | Tests 1 failed \\| 13 passed (14): the dashboard case red on (b) |\n| R3, the react page scope on the round-3 path | react-page.tsx = its 42226f9ed1 blob f70b8f4251 (HEAD blob ab712dface) | the react row fails at render: ReferenceError: useDataInvalidation is not defined in the page's error panel | not in the pin |\n| R3', the hook injected but never moving | ablation-replace: useDataInvalidation, in the scope becomes a function that always returns 0 (1 → 0), blob ab712dface → ade9a50713 | the react row: reads 1 → 1, no remount | not in the pin |\n\nThe round 1 to 3 rows on this head. They were not re-run through a census probe this round. Each block that sentence 5 names from those rounds has its own committed bus pin, and all of them ran green on fecce55c33 in one lock hold: Test Files 27 passed (27), Tests 171 passed (171). The files are ObjectGrid.invalidationRefetch-10035, ObjectChart.invalidationRefetch-10035, DetailView.invalidation, ListView.invalidationRefetch-10572, ObjectKanban.invalidationRefetch-10572, ObjectMetricWidget.invalidationRefetch-10572, ObjectCalendar.invalidationRefetch-10572, ObjectGallery.invalidationRefetch-10623, ObjectTimeline.invalidationRefetch-10623, ObjectGantt.invalidationRefetch-10035, ObjectMap.invalidationRefetch-10623, ObjectMap.busReread-10623, ObjectTree.invalidationRefetch-10778, ObjectPivotTable.invalidationRefetch-10778, ObjectDataTable.invalidationRefetch-10778, ObjectForm.invalidationRefetch-10572, formArmsInvalidation-10715, MasterDetailForm.busReread-10853, DatasetReportRenderer.invalidationRefetch-10814, DatasetWidget.invalidationRefetch-10815, LineItemsPanel.invalidationRefetch-10814, elementReaders.invalidationRefetch-10623, record-picker.busReread-10853, ObjectView.busReread-10887, DashboardFilterBar.busReread-10887, react-page-invalidation and drillDownReport.invalidationRefetch-10814.\n\n## H2 — the remount, reproduced on BASE\n\nThe new pin run against BASE PageView.tsx (byte-identical to BASE by blob hash 9de300582c) with the head pins kept, before any source edit: Tests 2 failed | 1 passed (3).\n\n- SchemaRenderer branch, red on (a): The page action REMOUNTED the page: the SchemaRenderer consumer mounted a second time — the object-gantt stand-in's instance counter read 2 after one api action.\n- interface branch, red on (a): The page action REMOUNTED the interface page — the interface-list-page node after the action was not the node before it.\n- lit control green: a change declared on the bus directly reached the metric and the stand-in once each, with no action run.\n\n## Pins and ablations\n\nPageView.refreshInPlace-10519.test.tsx renders PageView for real under MemoryRouter, AdapterCtx and SchemaRendererProvider, with the real console action runtime, the real page, page:accordion and action:button renderers, the real object-metric (plugin-dashboard) and object-form (plugin-form) blocks, and, on the interface branch, the real InterfaceListPage over plugin-list's ListView. The stand-in registered as object-gantt follows the ObjectView.refreshInPlace-10035 precedent: it reads the real bus hook, counts its own queries, and carries an instance id and a scroll box. The page action is a real api action (target: /api/v1/environments) through the runtime's apiHandler, an authenticated raw-HTTP call that fires no dataSource onMutation.\n\nAfter one page action:\n\n- (a) the same tree: the stand-in mounted exactly once (instance id unchanged), its scroll box is the same node with scrollTop still 120, the metric node is the same, the form's name input is the same node and still reads the half-typed User typed, and the collapsed accordion panel still has aria-expanded=\"false\"; on the interface branch the interface-list-page node and ListView's own refresh-button node are unchanged.\n- (b) each bus reader re-read exactly once: the metric's aggregate delta is 1, the stand-in's query delta is 1, and ListView's list query delta (its $top: 0 count probe excluded) is 1.\n- lit control: notifyDataChanged({ objectName: '*' }) with no action reaches the metric and the stand-in once each.\n\nRed on base, green on head, and two ablation legs, all on committed state with every restore spelled git checkout HEAD -- ABSOLUTE_PATH and proven by blob hash, git diff HEAD empty and git status clean:\n\n| leg | mutation (on-disk proof) | result over the pin, the ratchet and context-9673 |\n|:--|:--|:--|\n| A1, reverse run | PageView.tsx checked out at BASE (blob 9de300582c == BASE blob; key={refreshKey} sites 2, notifyDataChanged sites 0), head pins kept | Tests 5 failed | 2 passed (7): ratchet red (expected [ …(2) ] to deeply equal [], the two key sites), context-9673 red on both cases (+ \"refreshKey\": 0), the pin red on (a) on both branches; the ratchet's scan-path guard and the pin's lit control green. Restore: blob 8e96f4adbf == HEAD blob, diff empty, status clean |\n| A2, the host declares nothing | through ablation-replace.mjs: anchor notifyDataChanged({ objectName: '*' }); hit 1 → 0, replacement 0 → 1, blob 8e96f4adbf → 83a296ea0a | Tests 2 failed | 5 passed (7): the pin red on (b) on both branches (The metric did not re-read after the action, The list did not re-query after the action); (a), the lit control, the ratchet and context-9673 green. Restore by the tool: blob == HEAD, git diff HEAD empty |\n\nEach leg fails on the half the defect predicts and nowhere else, so each pin fails for the reason it names. No dist/ preflight applies: every test here imports PageView from src, and the root vitest alias map resolves every @object-ui/* specifier to source.\n\n## Gates\n\nRound 1, at 4620ba39d2:\n\n- Pins at head: the five PageView test files (PageView.refreshInPlace-10519, no-refresh-key-remount.ratchet, PageView.context-9673, PageView.test, page-kind-writing-end-9718): Test Files 5 passed (5), Tests 12 passed (12), VERDICT command-exit 0.\n- pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json, script name echoed), after turbo run build of the 28-task dependency closure (cache-restored, dist/index.d.ts present): VERDICT command-exit 0.\n- Lint, narrowed and measured: eslint --format json over the four touched files reads 4 files, 0 errors, 27 warnings, all @typescript-eslint/no-explicit-any, the warning-level rule the package already carries on every as any (the base PageView.tsx had them). Population: eslint --format json . in packages/app-shell (what CI's turbo run lint runs there) reads 1298 files, 0 errors, 3207 warnings. Invariance: eslint.config.js enables no type-aware linting (projectService / parserOptions.project absent), so this diff moves no untouched file's verdict.\n- check-changeset-presence: 4 source files of 1 released package changed, 1 changeset declared, exit 0. check:new-line-citations: 0 new citations. check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:control-bytes, check:phantom-deps, changeset:check (fixed, no-major), check:changeset-claims, check:pending-changeset-literals, check:shell-escape-residue, check:unreferenced-sources: all exit 0 with their OK line.\n- Declared to CI: the full app-shell test suite and the repo-wide pnpm test; the scope named above is the union of every test file that imports PageView or that this diff edits.\n- Round 2, at 25c9aa6d3a: the same five PageView pin files, Test Files 5 passed (5), Tests 12 passed (12), VERDICT command-exit 0. check-changeset-presence, check-changeset-no-major, check-changeset-overwrite, check:changeset-claims, check:control-bytes and check:new-line-citations each exit 0 there with their OK line. The type-check was not re-run this round, which stopped at the census; CI runs on 25c9aa6d3a.\n- Round 3, at 42226f9ed1 (the final commit): the same five PageView pin files, Test Files 5 passed (5), Tests 12 passed (12), VERDICT command-exit 0. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json, script name echoed), after turbo run build --concurrency=2 --filter=@object-ui/app-shell^... (Tasks: 28 successful, 28 total), in one lock hold: VERDICT command-exit 0. check-changeset-presence (4 source files of 1 released package, 1 changeset declared), check-changeset-no-major, check-changeset-overwrite (report-only: 1 added, 1 modified, the 9673 entry, declared bump unchanged), check:changeset-claims, check:control-bytes, check:new-line-citations (0 new citations), check:pending-changeset-literals and changeset:check's fixed-group half each exit 0 with their OK line. eslint --format json over the edited ratchet file: 1 file, 0 errors, 0 warnings. Source-text pins: no test or script quotes a sentence this round changes (the two 9673 sentences, this changeset's sentences 2 and 5, the ratchet titles).\n\nRound 4:\n\n- At the merged head e021bfaf24, before anything was added: the six app-shell test files that import or read PageView (the five above, plus PageView.editInStudioNeedsAuthoring-10899, which the merge brought): Test Files 6 passed (6), Tests 16 passed (16), VERDICT command-exit 0.\n- At the final head fecce55c33, in one lock hold: the census probe, Tests 8 passed (8); the same six PageView files, Test Files 6 passed (6), Tests 20 passed (20); the 27 reader pins (H1, round 4), Test Files 27 passed (27), Tests 171 passed (171); VERDICT command-exit 0. The other test that reads PageView.tsx, residue-namespaces-3546 in @object-ui/i18n: Tests 43 passed (43), VERDICT command-exit 0; the two in scripts/__tests__ ran in the scripts/ project below.\n- pnpm --filter @object-ui/app-shell type-check (echoed tsc --noEmit && tsc -p tsconfig.test.json), after turbo run build --concurrency=2 --filter=@object-ui/app-shell^... (Tasks: 28 successful, 28 total): VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles names the pin once, so the test program compiles it.\n- pnpm exec vitest run scripts/ --maxWorkers=2, the whole scripts/ project, so every scripts/__tests__ gate whose corpus reads a touched file: Test Files 177 passed | 2 skipped (179), Tests 5350 passed | 2 skipped (5352), VERDICT command-exit 0.\n- Each exits 0 with its OK line: check-changeset-presence (4 source files of 1 released package, 1 changeset declared), check-changeset-no-major, check-changeset-fixed, changeset:check, check-changeset-overwrite (report-only: 1 added, 2 modified, the 9673 and 10887-react-page entries, declared bumps unchanged), check:changeset-claims, check:pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new citations), check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:phantom-deps, check:unused-deps, check:shell-escape-residue and check:unreferenced-sources. check-governed-queue-guard --test over the 7 changed paths: NOT GOVERNED.\n- Lint, narrowed and measured: eslint --no-inline-config --format json over the four touched source and test files reads 4 files, 0 errors and 29 warnings, all @typescript-eslint/no-explicit-any. The pin carries 19, one more than its round-3 blob; PageView.tsx carries 5, as main's blob does; context-9673 carries 5 and the ratchet 0. eslint.config.js names no projectService and no parserOptions, so type-aware linting is off and the diff moves no untouched file's verdict. CI Lint is success on the head.\n- Eager closure, head against main. The only source file this PR changes against main is PageView.tsx. A console vite build at the head, and again with main's PageView.tsx blob (restored by blob hash afterwards), both read Console eager closure is 3105.0 KB gzipped across 330 of 2446 chunks (budget: 3149.4 KB, headroom: 44.4 KB), with equal gzip byte totals. PageView is its own lazy chunk, and the two eager sets differ only in the entry chunk's hashed name. CI Bundle Analysis is success on the head and on main f6fb83f0c5.\n- The full app-shell suite (869 test files): NOT MEASURED locally. The run was stopped after 20 minutes under the lock (its own PIDs, nobody else's) to free the shared lock. CI's Test (shard 1/8) to Test (shard 8/8) are success on fecce55c33.\n- CI on fecce55c33: 43 check runs, 40 success and 3 skipped (the two coverage runs and dependabot), Spec Main Shape Gate and Bundle Analysis among the successes.\n\n## Serial\n\nBefore the final push, origin/main was fetched into the private ref refs/issue-10519/main (805abe240c): one commit landed since BASE (objectui#10763), and it touched no file this branch touches (only two other cards' changesets). git merge-tree --write-tree HEAD refs/issue-10519/main exit 0 (clean). No merge commit was needed.\n\nRound 2: origin/main was fetched into the same private ref (195052fff6, 47 commits past 4620ba39d2's base, objectui#10809 and objectui#10836 among them) and merged as the merge commit 25c9aa6d3a (parents 4620ba39d2, 195052fff6). There were no conflicts. It was pushed fast-forward, with no rebase and no force.\n\nRound 3: origin/main was fetched into the same private ref (1c5ee334a2, 17 commits past 195052fff6, objectui#10873 among them) and merged as the merge commit c5b63fb514 (parents 25c9aa6d3a, 1c5ee334a2), with no conflicts, pushed fast-forward. Before the final push, origin/main had moved two more commits (24845c4f14: objectui#8284 and objectui#7189); neither touches a file this branch touches, and git merge-tree --write-tree HEAD refs/issue-10519/main at 42226f9ed1 exits 0 (tree fe8c05b333). No further merge was made.\n\nRound 4: origin/main was fetched into the private ref refs/issue-10519d/main (f6fb83f0c5, 82 commits past 1c5ee334a2, objectui#10898 and objectui#10973 among them). git merge-tree --write-tree against it exited 0 (tree 2808afb79b), and the merge commit e021bfaf24 (parents 42226f9ed1, f6fb83f0c5) carries exactly that tree. The one file both sides changed is PageView.tsx: main's hunk (PR objectui#10924, the useCanAuthorMetadata gate on the edit-in-studio button) and this branch's hunks do not touch the same lines, and both are kept. Pushed fast-forward, with no rebase and no force. Before the final push origin/main had not moved, and the open-PR file lists (objectui#11006, #10930, #10278, #5400) name neither PageView.tsx nor anything under packages/app-shell/src/views/.\n\n## Acceptance notes\n\n- The bare list key is the display list by design (plugin-list src/index.tsx); no finding.\n- Not filed, nit: useConsoleActionRuntime's onRefresh doc comment still describes the refresh as "bump a refresh key to re-fetch embedded data"; ObjectView still bumps one (plus the bus, objectui#10035), so the sentence is stale only for this host.\n- A Refresh button inside ListView's toolbar (refresh-button) is a user refresh affordance and is unrelated to PageView.onRefresh; the pin uses that node only as an identity witness.\n- Round 4, the calendar's loading placeholder (H1, round 4): ObjectView's non-grid fetch sets loading on every run, a bus re-read included, and ObjectCalendar swaps its grid for the placeholder while it is set. That is PR objectui#10973's note (c), recorded there as not filed. Not filed here either: carrier none. The month and the view mode live in ObjectCalendar's own state and are kept.\n- Round 4, the 10887-react-page changeset edit: the claim's file surface does not list that file. It is edited under the in-place rule for a same-class planned edit: a pending entry this branch makes false in the same release, fixed the way the 9673 entry was (a plain replacement, frontmatter unchanged, same changeset gate family). The seat may take it out and file it instead.\n- The release PR objectui#5400 deletes .changeset/9673-pageview-drop-context-spread.md and .changeset/10887-react-page-data-invalidation.md when it consumes them. If it lands first, both entries ship with their current sentences, which are true for that release, and this branch's edits to them become modify/delete conflicts, resolved by dropping the edits. That is the round-2 argument for the 9673 edit, and it covers both.\n\n---\n_Generated by Claude Code_\n"
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR objectui#10777 (Fixes #10519, round 4) · domain:ui seat #1 · 2026-09-29T01:34Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the claiming seat (claim 5877617401). Checked against GitHub and origin/main, ⛔ not against the reports.

    Checklist

    • Shape. Draft → main. The body opens Fixes #10519, then Clause-②: no at line start. PR assignee os-steve. The seat applied the dev's full pr_body_replacement from report 5878925072, and it read back byte-identical.

    • Scope. 7 files, +676 / −30 against the merge base. PageView stops remounting after a page action: onRefresh declares on the data-invalidation bus with the '*' scope, and both key={refreshKey} sites leave. It also carries:

      • the pins, including the round-4 describe for the object-view kanban, gallery and calendar rows and the dashboard optionsFrom filter;
      • the ratchet entry;
      • two pending-changeset edits: the 9673 rewrap, and the declared one-word tense edit in 10887-react-page-data-invalidation.md;
      • this PR's changeset.
    • Governed-surface predicate. 7 paths: NOT governed. Tier S: queue landing.

    • Checks on head fecce55c3. 43 check-runs: 40 success, 3 skipped, 0 failure. mergeable_state reads clean. The review measured git merge-tree clean against main 0eb9f36a, and none of the 3 commits since the merge base touches this PR's paths.

    • Commits. Seven across the four rounds, every trailer the model-free Claude, and no model name in any message, read by the seat.

    • Contract review 5881932045: Served-tier: CONTRACT_REVIEW_TIER, head fecce55c33fe9ff3f116f03148719d224016ae87 (the current head), PASS. It was rendered by the director seat (session_01AsCNgFBs8HCjwhyHQsFbx3) on the maintainer's ask 「契约复审 10777」, independent of this seat and of the dev.

      • The reader list was checked block by block, 28 bus readers at main. The first FAIL's missing readers (tree, pivot, data table) are readers now.
      • The pins are two-sided: the same mounted tree, and exactly one re-read.
      • Every item the two FAIL records 5856227410 / 5862674975 and ruling 5862691924 owed is at the head.
      • The kind: 'react' consequence is disclosed.
      • The out-of-surface tense edit is accepted.

      This seat's own at-tier reviewer stopped on repeated quota refusals and posted nothing, so this record is the review of record.

    「Readers first」 holds. objectui#10887 landed through PRs objectui#10898 and #10973. The round-4 census found no remount-only reader, and the review confirmed a reader for every block the changeset names.

    Residue, recorded, not this PR's (the review's ③):

    • AppContent.tsx's externalRefreshKey into ObjectView is a prop-driven refresh outside this card.
    • kind: 'react' pages that never name the injected nonce now get no refresh from a page action. This is disclosed in the changeset, and the guide teaches the nonce.
    • The calendar region's 'Loading calendar…' swap during a bus re-read is PR objectui#10973's note (c), already carried by objectui#10982.

    Landing: not governed, green, at-tier PASS on the current head ⇒ pr_ready then automerge_enable through the relay, in this act. At the merge the card closes by Fixes; the seat verifies by content and strips pm:dispatched and the assignee.

    domain:ui seat #1 · ACCEPT · 2026-09-29T01:34Z


    Generated by Claude Code

  11. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and verified ⇒ card closed · domain:ui seat #1 · 2026-09-29T01:56Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). PR objectui#10777 (Fixes #10519, round 4) merged through the queue as a33cf7e92 at 2026-09-29T01:49Z. The card closed completed by Fixes.

    • Verified by content on origin/main. The squash commit's patch-id equals the PR head fecce55c3's diff against its merge base, and its only co-author trailer is the model-free Claude.
    • Review. The director seat's at-tier record 5881932045 (PASS on that head) is the review of record. ACCEPT: 5881953794.

    Close-out. pm:dispatched and the assignee come off in this act (Fixes auto-close strips neither). The claim 5877617401 is spent. The branch was deleted by the merge.

    What landed. PageView no longer remounts the page after a page action. Its readers re-read in place through the data-invalidation bus under the '*' scope, 「readers first」 (decision B 5830213285) held across four rounds, and the readers came from objectui#10853 and #10887.

    Residue, carried elsewhere or disclosed:

    • AppContent.tsx's externalRefreshKey into ObjectView is a prop-driven refresh, outside this card.
    • kind: 'react' pages that never name the injected useDataInvalidation nonce now get no refresh from a page action. This is disclosed in the changeset.
    • The calendar's loading swap during a bus re-read is objectui#10982.

    domain:ui seat #1 · landed · 2026-09-29T01:56Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    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 seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions