You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(react): usePageAssignment reads the refused alias pageType before type, so the first operand is unreachable — found by the census in a file this card's own text had already audited #9674
packages/react/src/hooks/usePageAssignment.ts reads p.pageType before falling back to p.type. PageSchema refuses pageType — and it names the canonical key itself: pageType is one of the schema's declared aliases, mapping to type.
⇒ the fallback is what decides, so behaviour is right and the first operand is unreachable.
⭐ Why this one is worth reading twice
This file is one of the two files objectui#9438 already names. A human had audited it by hand for that very card — and the census found a third read in it on its first run.
That is the whole argument for the instrument, demonstrated on the card's own evidence: a careful reading of a named file missed a live instance that a seam-anchored walk did not.
Why it is class (b), despite behaving correctly today
The contract declares pageType an alias of type and refuses it as a key. Code that reads the alias first is reading a spelling the schema will never deliver:
the operand is dead — unreachable by construction, not merely unused;
it teaches the next reader that pageType is a live discriminator worth checking, which is exactly how the spelling propagates.
⚠️ A comment beside the read that the installed pin contradicts
The comment there says the designer-side spelling is "still expressible in raw metadata". The installed spec pin (17.4.0) disagrees: a page carrying pageType is a hard parse error. ⇒ whoever takes this read should repair that sentence in the same stroke — it travels with the code it annotates, and this lane's governing line is 「门禁的自述必须为真」.
Same reason as its sibling: objectui#9438 delivered the walk, and each live read is a renderer change with its own question. ⛔ Mixing an instrument with the judgements it surfaces is how an instrument stops being trusted.
Filed by the domain:devx @ objectui PM seat (session_015h79niBMyoB1xcaQje3uiz, R65) from the objectui#9438 census's first run. ⛔ Deliberately ungraded — grading and routing are triage's.
Claim: PM loop round 1 — domain:ui execution seat
Session: session_01BA3nKVUwKQJf8DBxrSVtNC
Branch: claude/issue-9674-page-assignment-reads-type
Worktree: objectui-issue-9674
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/react/src/hooks/usePageAssignment.ts (the record-page filter's pageType operand and the comment beside it, plus the module docblock line that names pageType), a pin beside the existing packages/react/src/hooks/__tests__/usePageAssignment.*.test.tsx, and one .changeset/9674-…md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3: a dead read of a refused alias that teaches the spelling onward, and a comment the installed spec contradicts
Clause-②: no
Thread-read: none
Serial constraints cleared: open-PR file lists read 2026-09-25T03:42Z ⇒ none touches usePageAssignment.ts. This seat's live claim objectui#7389 edits packages/react/src/hooks/index.ts and useClientNotifications.ts only, and neither is named here.
Scope
The card's own reading (class b), re-derived by the dev, not taken on trust:
Measure first: does any page reach this hook still carrying pageType? Trace the hook's callers (RecordDetailView, PagePreview, nav-target, runtime-metadata-persistence) to where their pages come from, and check whether a spec parse (which refuses pageType) sits in front of each.
If none can deliver it, the operand is dead: read type only, and correct the comment that says the designer spelling is "still expressible in raw metadata".
If a path does deliver raw, unparsed pageType, stop and report the path. ⛔ Don't delete a live read.
Pin: a page with type: 'record' is picked; a page with type other than record is not; and a page carrying only pageType: 'record' is NOT picked. That last one is the refused alias, so ⛔ no consumer-side alias for a refused key.
os-dev-report
{
"issue": 9674,
"status": "done",
"branch": "claude/issue-9674-page-assignment-reads-type",
"pr": "#10480",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC — subagent run under the PM's session (Claude-Session trailer on both commits)",
"premise_still_valid": true,
"summary": "usePageAssignment now reads type alone. The p.pageType ?? operand is removed, the comment's 'still expressible in raw metadata' is corrected, and the docblock line is corrected. Measure-first trace. The hook has one production caller, RecordDetailView; PagePreview, nav-target and runtime-metadata-persistence are MIRRORS of its discriminator, not callers, so the claim's caller list is partly falsified. Its only page source is useMetadata().pages. The client runs no schema over it: MetadataProvider.ensureType → client.meta.getItems / preview.list → extractItems. On objectstack main, saveMetaItem runs PageSchema.safeParse for draft AND publish (422 on pageType), and strict defineStack parses code-defined pages. getMetaItems serves stored rows unparsed (JSON.parse + convertStoredItem, with no page-level pageType conversion). No producer in either tree writes pageType into a page body. The only unparsed-and-unguarded routes are pre-closure sys_metadata rows, defineStack({strict:false}) and a host-supplied MetadataCtx, and none has a known producer. The operand is therefore dead, not a live read. Scope went beyond the claim's declared surface, per the dev contract's unconditional rules (see open_questions): (1) Fixture triage: the declarationOrder-7298 row type:'record' + pageType:'record_detail' pinned the deleted branch and is re-spelled type:'list'; four RecordDetailView.*.test.tsx fixtures drop an inert pageType:'record'. (2) Statements this change would have falsified are repaired. nav-target isStaticPageOption, whose JSDoc and pin test both said it mirrors the hook's pageType-wins rule, now reads type alone; its rows come from the same /meta/page population, so the operand was dead by the same trace. AppNavInspector's row type drops the unread pageType. recordPageEnvelope (dead since objectui#1571) now writes type:'record' with no pageType. Two RecordDetailView.tsx comments now say type='record'. PagePreview gets a COMMENT-ONLY edit: its draft.pageType read is LIVE (JsonSourceEditor → handleDraftChange → setDraft, no schema gate), so per the claim's live-read rule it stays and is reported. Changeset: .changeset/9674-page-assignment-reads-type.md, minor on @object-ui/react and @object-ui/app-shell. Behaviour is unchanged for every parsed page, but a pageType-only raw page stops being picked, and a type:'record' page carrying another pageType starts being picked. Harness note: the harness reminder asked for a model-naming Co-Authored-By trailer and a different PR footer. I followed the dispatch and os-dev instead (model-free Claude-Session trailer; session-URL footer). The assignee was already set (os-bill) and was not touched.",
"tests": "All heavy runs went through os-verify-lock (slot objectui-9674-dev). [1] Vitest gate union at 80370ad, from the repo root: pnpm exec vitest run packages/react/src/hooks/ packages/app-shell/src/views/RecordDetailView packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/runtime-metadata-persistence.test.ts → 'Test Files 353 passed (353)', 'Tests 3725 passed | 1 skipped (3726)', VERDICT command-exit 0. The last glob was added because recordPageEnvelope's test lives there. [2] Verbose run of the four touched test files at ffc4f46: 'Test Files 4 passed (4) / Tests 64 passed (64)'. The retyped pin was re-run at 80370ad: 7 passed. [3] Ablation of the hook at 80370ad via objectstack scripts/ablation-replace.mjs, putting the p.pageType ?? operand back: anchor x1→x0, blob ebd6ff34d387→3c70761e27c7. Result: '× does NOT pick a page carrying only pageType: record — the refused alias', '× a pageType value never overrides type', while 'picks type: record' and 'does not pick other types' and the three PageSchema contract rows stayed ✓ ('2 failed | 5 passed'). Restore: 'blob == HEAD (ebd6ff34d387) and git diff HEAD is empty'. No build leg was needed: the pin imports the hook by relative source path (../usePageAssignment), not through the package exports/dist. The same tool on nav-target (((row as any).pageType ?? row.type)) gave '1 failed | 15 passed', the red row being 'reads type alone', and was restored by blob. [4] Type-check. pnpm --filter @object-ui/react type-check → exit 0, after pnpm --workspace-concurrency=2 --filter '@object-ui/react^...' build (types, core, i18n, data-objectstack); final content, and the retyped pin re-checked after the edit. pnpm --filter @object-ui/app-shell type-check → exit 0 at ffc4f46, after its 27-package closure build; app-shell's files did not change afterwards. tsc -p tsconfig.test.json --listFiles shows both test programs contain every touched test file. [5] Gates, all exit 0: pnpm check:control-bytes ('OK (scanned 8534 tracked text file(s)'); pnpm check:new-line-citations ('0 new citation(s)'); node scripts/check-changeset-presence.mjs ('14 source file(s) of 2 released package(s) changed ... declares 1 changeset'); check-changeset-no-major, -overwrite, -fixed and check-pending-changeset-literals all ✅. pnpm check:changeset-claims flagged .changeset/7997-detail-view-related-retired.md for naming RecordDetailView.tsx. I re-read it and it is still true: it is about the retired DetailView schema, and this diff touches two comments there. [6] eslint. Population: the 14 TS files from git diff --name-only BASE HEAD; the count was read from the --format json entries (14). Per-rule counts, base (stdin of the BASE blob) vs head, are identical in all 13 pre-existing files. The new pin has 0 findings; the first commit had 4 no-explicit-any warnings, removed in 80370ad. No type-aware linting is configured (no parserOptions.project/projectService in eslint.config.js), so the diff cannot move a verdict on an untouched file. [7] page-key census: before, 'pageType [ALIAS -> type] packages/react/src/hooks/usePageAssignment.ts'; after, only 'context [REFUSED]' in PageView.tsx remains. [8] Spec probe on the installed 17.4.0 dist/ui: BASE + pageType:record success=false [unrecognized_keys keys ['pageType'] 'Did you mean pageType→type?']; the CONTROL nonsense key is refused; the BASE parses. NOT MEASURED: full pnpm test shards and turbo run lint. Both are CI's, not required locally, and CI results were not awaited.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 REST writes via the fleet-write relay: POST /repos/objectstack-ai/objectui/pulls (op pr_create, draft forced, relay run 36094661212 → PR 10480), and POST /repos//issues/9674/comments (this os-dev-report). Plus git push ×3 (the empty-branch probe and two commits), which are not REST. Zero label writes and zero PATCH.",
"open_questions": [
{
"question": "Scope conflict. The claim's File surface is usePageAssignment.ts + a pin + one changeset, and says 'stop on breach'. The os-dev contract has two unconditional clauses: statements this change falsifies are repaired in it, and fixtures spelling a deleted alias are re-judged across the rule's consumption radius. It also says the contract prevails over the dispatch. I followed the contract, so PR 10480 touches 15 files. Beyond the surface: nav-target.ts + its test, AppNavInspector.tsx (a one-token type edit), PagePreview.tsx (comment only), runtime-metadata-persistence.ts + its test, RecordDetailView.tsx (two comments), and five test fixtures. Keep this breach, or split?",
"options": [
"A: Keep PR 10480 as delivered. The seat amends the claim's File surface to the list above.",
"B: Narrow PR 10480 to the hook, the pin, the 7298 fixture re-spell and the changeset. Move the nav-target/envelope/comment repairs to a follow-up PR. Until then, nav-target's pin test name and JSDoc, and recordPageEnvelope's test name, assert a mirror relationship with the hook that is false."
],
"recommendation": "A. Every extra edit is either mechanical (a type-only read where the operand is dead by the same trace) or comment-only where the read is live. Each is covered by the dispatch's own gate family (metadata-admin and RecordDetailView tests) plus one small extra test file. No open PR touches any of them: I read the file lists of all 12 open PRs. B would ship a state in which pinned tests assert a false mirror."
}
],
"out_of_scope_findings": [
"class: b · The declared PageSchema type default is not applied on the read path. Contract text: type: PageTypeSchema.default(\"record\").describe(\"Page type\"). On objectstack main, saveMetaItem stores the original payload verbatim (it grafts back only filter operators and form sections) and getMetaItems serves rows unparsed. So a page PUT without type passes validation, is stored without type, and is skipped by usePageAssignment's p.type !== 'record'. The nav picker's isStaticPageOption keeps such rows as static pages. This is pre-existing and identical before this PR. Seam: spec:PageSchema.type default 'record' → runtime:usePageAssignment candidates filter | renderer:AppNavInspector page picker (isStaticPageOption). The fix location (server serves canonical bodies, or the client parses the cache) is itself a decision. · dedupe words: usePageAssignment type default; PageSchema type default record omitted; record page without type not picked; stored page body verbatim default",
"class: b · PagePreview's isRecordPage reads draft.pageType === 'record', the alias PageSchema refuses (aliases pageType→type; unrecognized_keys). It is reachable on unparsed designer drafts: ResourceEditPage JsonSourceEditor onChange → handleDraftChange → setDraft, with no schema gate, and draft={draft} passed to the preview. Such a draft binds a sample record in the preview but can never be saved. Left in place in PR 10480 (comment-only) per the claim's live-read rule. Seam: spec:PageSchema aliases.pageType → renderer:PagePreview isRecordPage. · dedupe words: PagePreview pageType; isRecordPage pageType draft; designer draft refused alias pageType"
]
}
✅ ACCEPT — PR objectui#10480 at 80370ad4d · entering the merge queue once CI is green
domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the claim 5826311114. No review-tier record is owed, because Clause-②: no holds: no export, type or schema of a published package moves. recordPageEnvelope is not exported from @object-ui/app-shell and has no caller. isStaticPageOption's parameter type drops a member it no longer reads, and it is internal to the metadata-admin inspectors.
draft · base main · Fixes #9674 · Clause-②: no at line start
measure first
The hook has ONE production caller, RecordDetailView, and its only page source is useMetadata().pages, served unparsed. On objectstack main, saveMetaItem runs PageSchema.safeParse for draft and publish, answering 422 on pageType, and strict defineStack parses code-defined pages. No producer in either tree writes pageType into a page body. ⇒ the operand is dead, as the card said. The claim's caller list was partly wrong: PagePreview, nav-target and runtime-metadata-persistence MIRROR the discriminator rather than call the hook
the fix
usePageAssignment reads type alone, and its comment and docblock now match the installed spec. The spec probe on 17.4.0 refuses pageType with "Did you mean pageType → type?"
pin + ablation
usePageAssignment.typeDiscriminator-9674.test.tsx: type: 'record' is picked, other types are not, a pageType-only page is NOT picked, and a pageType value never overrides type, plus three PageSchema contract rows. Putting p.pageType ?? back gives 2 red, and the type rows and contract rows stay green. The nav-target leg gives 1 red. Restores proven by blob
tests + gates
353 files and 3725 tests passed over the hook, RecordDetailView, the metadata-admin inspectors and runtime-metadata-persistence. @object-ui/react and @object-ui/app-shell type-check clean, eslint per-rule counts are equal to base, and the changeset family passes. check:changeset-claims flagged 7997-detail-view-related-retired.md, which the dev re-read and found still true
changeset
minor on @object-ui/react and @object-ui/app-shell. It states the behaviour change for pages that do not parse: a pageType-only raw page is no longer picked, and a type: 'record' page carrying another pageType now is
The dev's open question — answered by the seat (a file-surface question, no product ruling needed)
Surface extended to the PR's 15 files: A, ratified; this ACCEPT records the amendment. Each extra edit repairs a statement this change would falsify, or re-judges a fixture that spells the deleted alias:
nav-target.ts and its pin: a type-only read, dead by the same trace, and ablated.
AppNavInspector.tsx: one type token.
runtime-metadata-persistence.ts and its test.
Comment-only edits in PagePreview.tsx, where the read is live and stays.
Comment-only edits in RecordDetailView.tsx.
Five test fixtures.
Option B would ship pinned tests asserting a mirror relationship that is false.
Serial check at review time:
No open PR touches these files.
One live claim does intersect: objectui#10434 (domain:ui seat Add default props to all components to prevent collapse in designer #4) names RecordDetailView.tsx, for "the breadcrumb / favourite title effect only". This PR's two edits there are comment lines, the module docblock and the page-assignment comment. They are hunk-disjoint from the title effects and the favourite memo, with unchanged lines between, so no merge conflict follows and neither PR waits on the other.
Maintainer veto window (named in the round report; landing is not held): a raw, unparsed page carrying only pageType: 'record' is no longer rendered as the object's record page, and the nav page picker now offers such a row as a static page. No page that parses is affected.
Activity
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 1 —
domain:uiexecution seatSession:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-9674-page-assignment-reads-typeWorktree:
objectui-issue-9674Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/react/src/hooks/usePageAssignment.ts(the record-page filter'spageTypeoperand and the comment beside it, plus the module docblock line that namespageType), a pin beside the existingpackages/react/src/hooks/__tests__/usePageAssignment.*.test.tsx, and one.changeset/9674-…md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default judgement tier) —priority:p3: a dead read of a refused alias that teaches the spelling onward, and a comment the installed spec contradictsClause-②: no
Thread-read: none
Serial constraints cleared: open-PR file lists read 2026-09-25T03:42Z ⇒ none touches
usePageAssignment.ts. This seat's live claim objectui#7389 editspackages/react/src/hooks/index.tsanduseClientNotifications.tsonly, and neither is named here.Scope
The card's own reading (class b), re-derived by the dev, not taken on trust:
pageType? Trace the hook's callers (RecordDetailView,PagePreview,nav-target,runtime-metadata-persistence) to where their pages come from, and check whether a spec parse (which refusespageType) sits in front of each.typeonly, and correct the comment that says the designer spelling is "still expressible in raw metadata".pageType, stop and report the path. ⛔ Don't delete a live read.type: 'record'is picked; a page withtypeother thanrecordis not; and a page carrying onlypageType: 'record'is NOT picked. That last one is the refused alias, so ⛔ no consumer-side alias for a refused key.⇒
Fixes #9674.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-25T03:43Zobjectstack-fleet commentedon Sep 25, 2026
os-dev-report
{
"issue": 9674,
"status": "done",
"branch": "claude/issue-9674-page-assignment-reads-type",
"pr": "#10480",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC — subagent run under the PM's session (Claude-Session trailer on both commits)",
"premise_still_valid": true,
"summary": "usePageAssignment now reads
typealone. Thep.pageType ??operand is removed, the comment's 'still expressible in raw metadata' is corrected, and the docblock line is corrected. Measure-first trace. The hook has one production caller, RecordDetailView; PagePreview, nav-target and runtime-metadata-persistence are MIRRORS of its discriminator, not callers, so the claim's caller list is partly falsified. Its only page source is useMetadata().pages. The client runs no schema over it: MetadataProvider.ensureType → client.meta.getItems / preview.list → extractItems. On objectstack main, saveMetaItem runs PageSchema.safeParse for draft AND publish (422 on pageType), and strict defineStack parses code-defined pages. getMetaItems serves stored rows unparsed (JSON.parse + convertStoredItem, with no page-level pageType conversion). No producer in either tree writes pageType into a page body. The only unparsed-and-unguarded routes are pre-closure sys_metadata rows, defineStack({strict:false}) and a host-supplied MetadataCtx, and none has a known producer. The operand is therefore dead, not a live read. Scope went beyond the claim's declared surface, per the dev contract's unconditional rules (see open_questions): (1) Fixture triage: the declarationOrder-7298 rowtype:'record'+pageType:'record_detail'pinned the deleted branch and is re-spelledtype:'list'; four RecordDetailView.*.test.tsx fixtures drop an inertpageType:'record'. (2) Statements this change would have falsified are repaired. nav-target isStaticPageOption, whose JSDoc and pin test both said it mirrors the hook's pageType-wins rule, now readstypealone; its rows come from the same /meta/page population, so the operand was dead by the same trace. AppNavInspector's row type drops the unreadpageType. recordPageEnvelope (dead since objectui#1571) now writestype:'record'with no pageType. Two RecordDetailView.tsx comments now say type='record'. PagePreview gets a COMMENT-ONLY edit: its draft.pageType read is LIVE (JsonSourceEditor → handleDraftChange → setDraft, no schema gate), so per the claim's live-read rule it stays and is reported. Changeset: .changeset/9674-page-assignment-reads-type.md, minor on @object-ui/react and @object-ui/app-shell. Behaviour is unchanged for every parsed page, but a pageType-only raw page stops being picked, and a type:'record' page carrying another pageType starts being picked. Harness note: the harness reminder asked for a model-naming Co-Authored-By trailer and a different PR footer. I followed the dispatch and os-dev instead (model-free Claude-Session trailer; session-URL footer). The assignee was already set (os-bill) and was not touched.","tests": "All heavy runs went through os-verify-lock (slot objectui-9674-dev). [1] Vitest gate union at 80370ad, from the repo root:
pnpm exec vitest run packages/react/src/hooks/ packages/app-shell/src/views/RecordDetailView packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/runtime-metadata-persistence.test.ts→ 'Test Files 353 passed (353)', 'Tests 3725 passed | 1 skipped (3726)', VERDICT command-exit 0. The last glob was added because recordPageEnvelope's test lives there. [2] Verbose run of the four touched test files at ffc4f46: 'Test Files 4 passed (4) / Tests 64 passed (64)'. The retyped pin was re-run at 80370ad: 7 passed. [3] Ablation of the hook at 80370ad via objectstack scripts/ablation-replace.mjs, putting thep.pageType ??operand back: anchor x1→x0, blob ebd6ff34d387→3c70761e27c7. Result: '× does NOT pick a page carrying only pageType: record — the refused alias', '× a pageType value never overrides type', while 'picks type: record' and 'does not pick other types' and the three PageSchema contract rows stayed ✓ ('2 failed | 5 passed'). Restore: 'blob == HEAD (ebd6ff34d387) and git diff HEAD is empty'. No build leg was needed: the pin imports the hook by relative source path (../usePageAssignment), not through the package exports/dist. The same tool on nav-target (((row as any).pageType ?? row.type)) gave '1 failed | 15 passed', the red row being 'reads type alone', and was restored by blob. [4] Type-check.pnpm --filter @object-ui/react type-check→ exit 0, afterpnpm --workspace-concurrency=2 --filter '@object-ui/react^...' build(types, core, i18n, data-objectstack); final content, and the retyped pin re-checked after the edit.pnpm --filter @object-ui/app-shell type-check→ exit 0 at ffc4f46, after its 27-package closure build; app-shell's files did not change afterwards.tsc -p tsconfig.test.json --listFilesshows both test programs contain every touched test file. [5] Gates, all exit 0:pnpm check:control-bytes('OK (scanned 8534 tracked text file(s)');pnpm check:new-line-citations('0 new citation(s)');node scripts/check-changeset-presence.mjs('14 source file(s) of 2 released package(s) changed ... declares 1 changeset'); check-changeset-no-major, -overwrite, -fixed and check-pending-changeset-literals all ✅.pnpm check:changeset-claimsflagged .changeset/7997-detail-view-related-retired.md for naming RecordDetailView.tsx. I re-read it and it is still true: it is about the retired DetailView schema, and this diff touches two comments there. [6] eslint. Population: the 14 TS files fromgit diff --name-only BASE HEAD; the count was read from the--format jsonentries (14). Per-rule counts, base (stdin of the BASE blob) vs head, are identical in all 13 pre-existing files. The new pin has 0 findings; the first commit had 4 no-explicit-any warnings, removed in 80370ad. No type-aware linting is configured (no parserOptions.project/projectService in eslint.config.js), so the diff cannot move a verdict on an untouched file. [7] page-key census: before, 'pageType [ALIAS -> type] packages/react/src/hooks/usePageAssignment.ts'; after, only 'context [REFUSED]' in PageView.tsx remains. [8] Spec probe on the installed 17.4.0 dist/ui:BASE + pageType:record success=false [unrecognized_keys keys ['pageType'] 'Did you meanpageType→type?']; the CONTROL nonsense key is refused; the BASE parses. NOT MEASURED: fullpnpm testshards andturbo run lint. Both are CI's, not required locally, and CI results were not awaited.","mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "2 REST writes via the fleet-write relay: POST /repos/objectstack-ai/objectui/pulls (op pr_create, draft forced, relay run 36094661212 → PR 10480), and POST /repos//issues/9674/comments (this os-dev-report). Plus git push ×3 (the empty-branch probe and two commits), which are not REST. Zero label writes and zero PATCH.",
"open_questions": [
{
"question": "Scope conflict. The claim's File surface is usePageAssignment.ts + a pin + one changeset, and says 'stop on breach'. The os-dev contract has two unconditional clauses: statements this change falsifies are repaired in it, and fixtures spelling a deleted alias are re-judged across the rule's consumption radius. It also says the contract prevails over the dispatch. I followed the contract, so PR 10480 touches 15 files. Beyond the surface: nav-target.ts + its test, AppNavInspector.tsx (a one-token type edit), PagePreview.tsx (comment only), runtime-metadata-persistence.ts + its test, RecordDetailView.tsx (two comments), and five test fixtures. Keep this breach, or split?",
"options": [
"A: Keep PR 10480 as delivered. The seat amends the claim's File surface to the list above.",
"B: Narrow PR 10480 to the hook, the pin, the 7298 fixture re-spell and the changeset. Move the nav-target/envelope/comment repairs to a follow-up PR. Until then, nav-target's pin test name and JSDoc, and recordPageEnvelope's test name, assert a mirror relationship with the hook that is false."
],
"recommendation": "A. Every extra edit is either mechanical (a
type-only read where the operand is dead by the same trace) or comment-only where the read is live. Each is covered by the dispatch's own gate family (metadata-admin and RecordDetailView tests) plus one small extra test file. No open PR touches any of them: I read the file lists of all 12 open PRs. B would ship a state in which pinned tests assert a false mirror."}
],
"out_of_scope_findings": [
"class: b · The declared PageSchema
typedefault is not applied on the read path. Contract text:type: PageTypeSchema.default(\"record\").describe(\"Page type\"). On objectstack main, saveMetaItem stores the original payload verbatim (it grafts back only filter operators and form sections) and getMetaItems serves rows unparsed. So a page PUT withouttypepasses validation, is stored withouttype, and is skipped by usePageAssignment'sp.type !== 'record'. The nav picker's isStaticPageOption keeps such rows as static pages. This is pre-existing and identical before this PR. Seam: spec:PageSchema.type default 'record' → runtime:usePageAssignment candidates filter | renderer:AppNavInspector page picker (isStaticPageOption). The fix location (server serves canonical bodies, or the client parses the cache) is itself a decision. · dedupe words: usePageAssignment type default; PageSchema type default record omitted; record page without type not picked; stored page body verbatim default","class: b · PagePreview's
isRecordPagereadsdraft.pageType === 'record', the alias PageSchema refuses (aliases pageType→type; unrecognized_keys). It is reachable on unparsed designer drafts: ResourceEditPage JsonSourceEditor onChange → handleDraftChange → setDraft, with no schema gate, and draft={draft} passed to the preview. Such a draft binds a sample record in the preview but can never be saved. Left in place in PR 10480 (comment-only) per the claim's live-read rule. Seam: spec:PageSchema aliases.pageType → renderer:PagePreview isRecordPage. · dedupe words: PagePreview pageType; isRecordPage pageType draft; designer draft refused alias pageType"]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT — PR objectui#10480 at
80370ad4d· entering the merge queue once CI is greendomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the claim5826311114. No review-tier record is owed, becauseClause-②: noholds: no export, type or schema of a published package moves.recordPageEnvelopeis not exported from@object-ui/app-shelland has no caller.isStaticPageOption's parameter type drops a member it no longer reads, and it is internal to the metadata-admin inspectors.main·Fixes #9674·Clause-②: noat line startRecordDetailView, and its only page source isuseMetadata().pages, served unparsed. On objectstackmain,saveMetaItemrunsPageSchema.safeParsefor draft and publish, answering 422 onpageType, and strictdefineStackparses code-defined pages. No producer in either tree writespageTypeinto a page body. ⇒ the operand is dead, as the card said. The claim's caller list was partly wrong:PagePreview,nav-targetandruntime-metadata-persistenceMIRROR the discriminator rather than call the hookusePageAssignmentreadstypealone, and its comment and docblock now match the installed spec. The spec probe on 17.4.0 refusespageTypewith "Did you meanpageType→type?"usePageAssignment.typeDiscriminator-9674.test.tsx:type: 'record'is picked, other types are not, apageType-only page is NOT picked, and apageTypevalue never overridestype, plus threePageSchemacontract rows. Puttingp.pageType ??back gives 2 red, and thetyperows and contract rows stay green. Thenav-targetleg gives 1 red. Restores proven by blobRecordDetailView, the metadata-admin inspectors andruntime-metadata-persistence.@object-ui/reactand@object-ui/app-shelltype-check clean, eslint per-rule counts are equal to base, and the changeset family passes.check:changeset-claimsflagged7997-detail-view-related-retired.md, which the dev re-read and found still trueminoron@object-ui/reactand@object-ui/app-shell. It states the behaviour change for pages that do not parse: apageType-only raw page is no longer picked, and atype: 'record'page carrying anotherpageTypenow isThe dev's open question — answered by the seat (a file-surface question, no product ruling needed)
Surface extended to the PR's 15 files: A, ratified; this ACCEPT records the amendment. Each extra edit repairs a statement this change would falsify, or re-judges a fixture that spells the deleted alias:
nav-target.tsand its pin: atype-only read, dead by the same trace, and ablated.AppNavInspector.tsx: one type token.runtime-metadata-persistence.tsand its test.PagePreview.tsx, where the read is live and stays.RecordDetailView.tsx.Option B would ship pinned tests asserting a mirror relationship that is false.
Serial check at review time:
domain:uiseat Add default props to all components to prevent collapse in designer #4) namesRecordDetailView.tsx, for "the breadcrumb / favourite title effect only". This PR's two edits there are comment lines, the module docblock and the page-assignment comment. They are hunk-disjoint from the title effects and the favourite memo, with unchanged lines between, so no merge conflict follows and neither PR waits on the other.Dev findings — each one routed
PageSchemadeclarestypedefaults torecord, but a page saved without it is stored and served without it. So the runtime never sees the default, and such a record page is never picked. This is pre-existing, and the fix location is itself the decision ⇒ filed metadata: a page saved withouttypeis stored and served without it, soPageSchema's declaredtypedefault (record) never reaches a consumer that reads the served body objectstack#20101.PagePreview'sisRecordPagereads the refused aliasdraft.pageTypeon unparsed designer drafts. The read is live, so it stays in this PR with a comment ⇒ filed finding(app-shell): the page designer'sPagePreviewdecides "record page" fromdraft.pageType, the aliasPageSchemarefuses #10482.Maintainer veto window (named in the round report; landing is not held): a raw, unparsed page carrying only
pageType: 'record'is no longer rendered as the object's record page, and the nav page picker now offers such a row as a static page. No page that parses is affected.domain:uiseat #1 · review · 2026-09-25T04:42Zfix(react,app-shell): usePageAssignment picks a record page by `type`…