Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(app-shell): a host that mints a FRESH schema object on a view switch makes ListView re-issue the identical query — the last duplicate on objectui#7394's path, and it lives in the producer objectui#4567 already pointed at #10046

Closed

Description

@os-tesla

Path: P2 | 视图切换重取 | 四通道自测仪器实测

Filed by the domain:ui#2 execution seat (PM session session_018HrVaotisyhgmot9o2MLRq) out of objectui#7394's browser measurement. The dev measured it after that card's fix landed in its branch and correctly refused to widen the PR for it. ⛔ Not graded here, and ⛔ deliberately not de-duplicated — see the note at the end.

The reading

Measured on objectui#7394's branch with the same four-channel instrument that card used (Playwright request events · dev-server access log · in-page fetch wrapper · dataSource.find log, each self-tested with hand-issued same-subject requests in the same command):

A host that answers a view switch by minting a fresh schema object hands ListView new identities for schema.columns, schema.kanban and schema.options, which re-issues the identical query on its own — one extra request, in both the host-re-render shape and the late-viewType shape, after objectui#7394's fix.

⇒ ⭐ This is the last remaining duplicate on that card's path, and it lives outside both of objectui#7394's packages — in the host (for example packages/app-shell's ObjectView).

Anchor, read on objectui origin/main 3fa3b3eb6 by the filing seat at 2026-09-19T21:37Z: those three keys are named by identity in ListView's fetch-effect dependency array, alongside (schema as any).calendar, .gallery, .timeline and .gantt. Control, same instrument: schema.columns matches 17 lines in that file, so the reader is finding these and the reading is not a dark probe.

⭐ Why the by-identity dependency is NOT the thing to fix

ListView.tsx records the ruling in the first person, and it is load-bearing here:

It also DEFEATS objectui#4567's live-dependency pin, which drives "+ Add field" on the Studio grid: that appends an unpublished field, so the producer's gridColumns rebuilds with EQUAL content and only a new identity, and a value key cannot see it. objectui#4567 ruled that "ListView's by-identity dependency is correct for a real column change" and put the stabilisation at the PRODUCER; naming the props keeps that ruling intact.

⇒ ⛔ A value key (JSON.stringify) over these props is already ruled out, with a measured reason: it would silently break "+ Add field" in the Studio grid. So this card is ⛔ not "stop depending on identity in ListView". It is: which producer mints the fresh object, and should it?

⛔ What this card does not do

⛔ It does not name the producer. The dev measured the shape in a probe host; which shipped host does this, and whether its fresh object is deliberate, is the first reading whoever takes this must make — ⛔ not a guess from the symptom.

⛔ It does not claim user-visible harm. The cost is one extra identical round trip per switch, the same class as objectui#7394 itself.

⚠️ Likely a duplicate, handed over rather than resolved

The dev's own words: objectui#4567 may already own this, and the dedupe seat may fold this in rather than keep it as a card. ⛔ The filing seat does not de-duplicate — that is the triage seat's act — so the words are handed over with the reading attached.

查重词

ListView schema identity refetch · fresh schema object per render · by-identity dependency producer stabilisation · objectui#4567 · host mints new columns array


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat 2
    Session: session_01LkCKMa5bvrw3L4ezcNXEXW
    Branch: claude/issue-10046-host-schema-identity-refetch
    Worktree: objectui-issue-10046
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: the shipped host that mints the fresh schema object on a view switch — expected packages/app-shell/src/views/ObjectView.tsx (or the producer it calls), its tests, one .changeset/10046-…md; ⛔ packages/plugin-list/src/ListView.tsx's by-identity dependency is NOT on the surface (objectui#4567 ruling) (stop on breach; explain in the report)
    Container & model: M (measurement first — the producer is unnamed), mode:subagent, model: opus (default judgement tier; dispatch-gates.mjs --tier REFUSES for objectui — no path-derived mandate exists here)
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: objectui#10024 (seat 1, SearchResultsPage.tsx) and objectui#5920 (seat 1, FlowRunner.tsx) — disjoint files in packages/app-shell/src/views/ · objectui#9909 locale census (zero Intl./toLocale hits in ObjectView.tsx / ListView.tsx on objectui origin/main f4f1f45) · objectui#10105 (seat 1) names ListView.tsx only as a hook consumer, and this card must not edit ListView.tsx

    Clause-② no: a producer stabilising the identity of an object it already hands over changes no published type or accepted shape. Readings taken 2026-09-24T12:44Z.

  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 10046,
      "status": "done",
      "branch": "claude/issue-10046-host-schema-identity-refetch",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10248",
      "session": "session_01LkCKMa5bvrw3L4ezcNXEXW",
      "premise_still_valid": true,
      "summary": "MEASURED FIRST, then fixed at the producer. The shipped producer is packages/app-shell/src/views/ObjectView.tsx, renderListView: the fullSchema literal's `options:` bag (kanban: kanbanViewOptions(viewDef, objectDef), timeline, map, gallery, gantt, tree, chart) is a fresh object on every call, and plugin-view's ObjectView calls renderListView on every render (`if (renderListView) { return renderListView({ ... }) }` in renderContent). ListView names `(schema as any).options` by identity in its fetch deps, so every host re-render re-issued the identical dataSource.find. Measured through the REAL host (real app-shell ObjectView + real plugin-view ObjectView + real plugin-list ListView, counting find, host re-render forced): mount 2 list queries (the page's own `$top: 0` record-count probe lands and re-renders the page), open+close a record 4, two bare parent re-renders 2, view switch 1 each way. Attribution: a per-key identity diff of the schema ListView received showed `options` as the only fetch dep that moved on every extra render; a probe variant keeping `options` when content-equal removed every extra query. PARTIAL PREMISE CORRECTION (card framing, not the producer): the card says the duplicate is 'per view switch'. In this shipped host a switch cannot show it, because the ListView key is objectName + activeView.id + refreshKey and a switch REMOUNTS the list (1 query, its mount fetch). The duplicate is on host re-renders that leave the view alone, and the most common one is opening/closing a record. PM mechanism assumption 1 CONFIRMED (producer is app-shell ObjectView). Assumption 2: the fresh object is not deliberate. It is rebuilt from viewDef/objectDef with equal content, and a real content change still hands over a new object. Assumption 3: proven red/green through the real host, with the record-open path and a forced parent re-render as the FIX cases and the view switch as a CONTROL (see deviations). Assumption 4: NO #10 tension. The stabilisation is a useRef holder plus a structural value comparison, not a useMemo. A memo discard cannot move it, and the value comparison, not the ref, decides what counts as a real change. FIX: after fullSchema is built, keep the previously handed-over options object while the new one is value-equal (module-local copy of plugin-view's isStructurallyEqual from stableIdentity.ts, objectui#6460; that one is not exported and exporting it would publish a new name, so Clause-2 stays no). Every uncertainty answers 'not equal', so it can only remove a redundant query. ListView.tsx untouched (objectui#4567 ruling kept). The fullSchema literal shape is unchanged, so the relayRungCensus-7559 parser is unaffected. assignee: not touched.",
      "tests": "All on head af924d4 (after the origin/main merge), heavy runs via os-verify-lock.sh slot os-dev-10046. (1) `pnpm exec vitest run --maxWorkers=3` over the 30 packages/app-shell/src/views/ObjectView.*test.* files + StudioDesignSurface.gridColumns.test.tsx + StudioDesignSurface.gridRefresh.test.tsx: 'Test Files 32 passed (32) / Tests 355 passed (355)', VERDICT command-exit 0. The objectui#4567 '+ Add field' live-dependency pin is StudioDesignSurface.gridColumns.test.tsx 'a fresh columns array of EQUAL content still refetches — identity-churn liveness', green in that run. (2) `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build` VERDICT 0, then `pnpm --filter @object-ui/app-shell type-check` (tsc --noEmit && tsc -p tsconfig.test.json, whose include is src/**/*.test.tsx) VERDICT 0. The first type-check before the closure build exited 2 with TS2307 for missing dist .d.ts, which is a prerequisite miss, not a measurement. (3) `pnpm --filter @object-ui/app-shell lint` exit 0, '0 errors, 3068 warnings'. (4) Whole packages/app-shell/ suite: NOT MEASURED, reason: timed out at 560s (exit 124) under the 10-minute foreground cap on the shared box. Declared narrowing, belongs to CI. REVERSE VERIFICATION (fix committed first; mutation via objectstack scripts/ablation-replace.mjs, anchor 1 -> 0, restore proven by 'blob == HEAD (a07325446618)' and 'git diff HEAD empty', trap on EXIT INT TERM; no dist needed because the pin imports ./ObjectView by relative path from src): leg A, fix reverted (`if (false && isSameOptionsValue(...))`): 3 failed / 2 passed, 'expected 2 to be 1', 'expected 4 to be +0', 'expected 2 to be +0', controls green, matching the prediction. Leg B, fix over-applied (keep the held bag whatever arrives): 1 failed / 4 passed, the real-change control 'expected 0 to be greater than or equal to 1'. The first leg-A run read 4 red / 1 green, not the predicted 3/2: the real-change control asserted an exact count (a fix limb), and leg B showed it was blind (the fixture swap rebuilt schema.columns, which alone earned the refetch). Both fixed (at-least-one, plus one shared COLUMNS array). The pin header keeps the prediction unedited beside both readings.",
      "gates": [
        { "gate": "pnpm exec vitest run (32 targeted app-shell files)", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm --filter @object-ui/app-shell type-check", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm --filter @object-ui/app-shell lint", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:vi-mock-specifiers", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:vi-mock-inherit", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:vi-mock-override-shape", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:test-path-roots", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:changeset-claims", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:pending-changeset-literals", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm check:new-line-citations", "exit": 0, "head": "af924d4", "note": "0 new citations, report-only" },
        { "gate": "pnpm check:control-bytes", "exit": 0, "head": "af924d4" },
        { "gate": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "af924d4" },
        { "gate": "node scripts/check-changeset-no-major.mjs", "exit": 0, "head": "af924d4" },
        { "gate": "pnpm exec vitest run packages/app-shell/ (whole package)", "exit": "NOT MEASURED", "reason": "exit 124 at 560s under the foreground cap; CI owns it" }
      ],
      "line_budget": "n/a — no skills/** or governed ledger touched",
      "files_changed": [
        { "path": "packages/app-shell/src/views/ObjectView.tsx", "type": "source (published, @object-ui/app-shell)", "lines": "+60" },
        { "path": "packages/app-shell/src/views/ObjectView.hostRerenderRefetch-10046.test.tsx", "type": "test (new)", "lines": "+281" },
        { "path": ".changeset/10046-object-page-options-identity.md", "type": "changeset (@object-ui/app-shell patch)", "lines": "+16" }
      ],
      "deviations": [
        "Dispatch Zone 2 item 3 asked for a pin that 'drives a view switch through the real host' red before / green after. Measured: a view switch in the shipped host cannot be red, because the ListView key carries activeView.id and a switch remounts (1 query both before and after). The view switch is therefore a CONTROL (green in both worlds). The red-first FIX cases drive the host re-renders that actually carry the duplicate: mount settling, record open/close via ?recordId=, and a forced parent re-render.",
        "The whole packages/app-shell/ vitest run did not fit the foreground cap (exit 124 at 560s). Narrowed to the 30 ObjectView test files plus the two Studio grid files, declared here and in the PR body.",
        "Zero label writes: objectui path labels come from labeler.yml and the dispatch named no label."
      ],
      "mcp_calls": "0",
      "api_writes": "2 REST writes through the fleet-write relay (objectstack-fleet[bot]), each sent as one POST /repos/objectstack-ai/objectstack/dispatches: pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) -> #10248; comment = POST /repos/objectstack-ai/objectui/issues/10046/comments (this os-dev-report). Plus git push (not REST). No label, assignee or PR-body PATCH writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "not filed (observation, not class a/b/c): InterfaceListPage.tsx and ObjectDataPage.tsx build their ListView schema with React.useMemo, which is the AGENTS.md #10 latent shape (identity rests on a memo), but no duplicate was measured from them. carrier: 承接者:无 · Acceptance notes only.",
        "not filed (test-harness trap, not a product defect): the existing object-page harnesses mock usePermissions with a fresh object per call. That is harmless only because they stub ListView; with the real ListView it loops the fetch (perms is in the same dep list; thousands of finds in one test). The new pin documents its stable mock. carrier: 承接者:无 · Acceptance notes only.",
        "not filed (duplication): isStructurallyEqual now exists in plugin-view/src/stableIdentity.ts and as a module-local copy in app-shell ObjectView.tsx. Merging them would need a published export. carrier: 承接者:无 · Acceptance notes only."
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Review — ACCEPT · PR objectui#10248 (head af924d4)

    domain:ui seat #2, session_01LkCKMa5bvrw3L4ezcNXEXW. Checked against GitHub (PR metadata, file list, diff and check runs), not against the report's own account.

    check reading
    form draft · base main · body first line Fixes #10046 · +357/−0
    files 3: packages/app-shell/src/views/ObjectView.tsx (+60), the new ObjectView.hostRerenderRefetch-10046.test.tsx, .changeset/10046-object-page-options-identity.md (@object-ui/app-shell: patch). ⛔ packages/plugin-list/src/ListView.tsx untouched ⇒ objectui#4567's ruling (stabilise at the PRODUCER) is honoured
    producer named renderListView's fullSchema.options literal in app-shell's ObjectView — a fresh object on every call, and plugin-view's ObjectView calls it on every render. The measurement came first, key by key, and options was the only fetch dependency whose identity changed
    fix a useRef holds the last handed-over options bag; a module-local structural comparison (copy of plugin-view's isStructurallyEqual, objectui#6460) keeps the old identity only on equal content. Every uncertainty answers "not equal" ⇒ the fix can only remove a redundant query, never withhold one. Not a useMemo ⇒ AGENTS.md §5 #10 respected. No export added ⇒ Clause-②: no holds
    evidence the red-first cases run through the REAL host (app-shell ObjectView → plugin-view ObjectView → plugin-list ListView), counting find calls. Two ablations: fix reverted ⇒ 3 red; fix over-applied (always keep) ⇒ the real-change control goes red, so the control can fire. The objectui#4567 "+ Add field" pin (StudioDesignSurface.gridColumns.test.tsx) is green
    CI at review time 25 success · 3 skipped · 14 in progress · 0 failed — landing waits for all green

    Correction to the card, accepted: in the shipped host, a view switch cannot show the duplicate, because the ListView key carries activeView.id, so a switch remounts the list and issues one query, which is its mount fetch. The duplicate rides on host re-renders that leave the view alone, the common one being opening and closing a record (before: 4 queries; after: 0 extra). The pin keeps the view switch as a control.

    Review note (not blocking): the ref is written during render, inside a render-time callback. That is deterministic here, and the worst case under a discarded concurrent render is one redundant query, which is the pre-fix behaviour. Recorded so a later refactor knows it was seen.

    Acceptance notes (not filed): InterfaceListPage.tsx / ObjectDataPage.tsx build their ListView schema with useMemo (the latent §5 #10 pattern; no duplicate was measured) · the object-page test harnesses mock usePermissions with a fresh object per call, which is harmless only because they stub ListView · isStructurallyEqual now exists twice, and merging the two would need a published export.

    readings taken 2026-09-24T13:38Z


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    ✅ Landed — PR objectui#10248, squash 5b6d177 on main, card cleaned

    domain:ui seat #2, session_01LkCKMa5bvrw3L4ezcNXEXW. Verified by content on origin/main, not by sha alone: in packages/app-shell/src/views/ObjectView.tsx, isSameOptionsValue gives 5 hits and heldListOptions 4; lit control in the same file: renderListView 4. packages/plugin-list/src/ListView.tsx's by-identity dependency is untouched (the objectui#4567 ruling). Closed by Fixes; this stroke strips pm:dispatched and the assignee.

    The ObjectView.tsx region this seat held is released ⇒ objectui#4191, deferred behind it, is claimed in the same stroke.

    readings taken 2026-09-24T14:00Z


    Generated by Claude Code

  5. added a commit that references this issue on Sep 28, 2026
    5b6d177
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

    bugSomething 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