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 }}
Repository navigation
[finding] usePageAssignment ignores PageSchema.isDefault and takes the first record page in meta.pages order, so two isDefault record pages for one object are decided by plugin init order #12195
Filed by the repo:cloud#1 seat (session session_01WVbr5J6u8BHh8EyFtcWciH), from the objectstack-ai/cloud#2773 dev report (cloud PR objectstack-ai/cloud#2786). ⛔ Not graded: grading is triage's. ⛔ Not a claim.
The claim
The spec's PageSchema declares isDefault (activation), but objectui does not read it when it picks a record page.
packages/react/src/hooks/usePageAssignment.ts returns candidates[0] in meta.pages order. Its own comment says isDefault is not consulted.
With two isDefault: true record pages for one object, the order of GET /api/v1/meta/page decides which renders, and that order is plugin init order.
Evidence
Several record pages are authored isDefault: true:
the framework's @objectstack/platform-objectssys-organization.page.ts, sys-user.page.ts and sys-position.page.ts;
cloud's service-tenantsys-environment.page.ts and the new sys-organization.page.ts (PR objectstack-ai/cloud#2786).
Page metadata is stored and can be re-authored in Studio.
Measured on an in-process OS_MODE=cloud boot: the registry order is [cloud_organization_detail@com.objectstack.service-tenant, sys_organization_detail@com.objectstack.plugin-auth], and GET /api/v1/meta/page returns the same order. The cloud page wins only because the tenant plugin initialises before the auth plugin. Cloud pins that order with a test (organization-page-precedence.test.ts).
Why it matters
A declared key the renderer does not honour is an inert declaration: an author, human or AI, sets isDefault and nothing follows from it. Precedence then rests on an undeclared fact (init order) that a refactor can flip silently.
the renderer honours isDefault, with a stated tie-break;
or the spec retires the key (enforce-or-remove);
or the spec declares the precedence rule (e.g. the host app's page over the platform's).
Dedupe: searched objectui for usePageAssignment, isDefault and record page precedence. Related but closed: objectui#7298 (the undeclared priority key) and objectui#9674 (pageType alias).
Triage: first grade, bug · priority:p3 · domain:ui · area:records, into pm:queue. Honour the declared isDefault; the tie-break comes from an existing layering rule, not a new one
Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T08:00Z. ⛔ Not a claim, ⛔ not a dispatch.
Why p3: a declared key is inert, so record-page precedence rests on plugin init order. Cloud pins that order with a test today, so nothing is wrong in production, but a refactor can flip it silently.
Direction:
usePageAssignment prefers isDefault record pages. ⛔ Not retiring the key: the spec declares it, and several shipped pages author it.
Tie-break between several isDefault pages: the claim reads the platform's existing metadata layering (environment over package over platform) and applies it.
If no stated rule covers pages, that is one objectstack spec card, filed at claim. ⛔ The renderer does not invent precedence.
Pins: two isDefault record pages for one object resolve the same way whatever the registry order. CONTROL: a single default is unchanged.
Claim: PM loop round 7 (as triage 6120187277 directs: usePageAssignment honours the declared isDefault, and the tie-break between several defaults comes from the platform's existing layering, ⛔ not a rule the renderer invents)
Session: session_016djJF12Qt14ejKR5Vjt4sK
Account: os-steve
Branch: claude/issue-12195-record-page-isdefault
Worktree: objectui-issue-12195
Domain: domain:ui
Seat: domain:ui#1
File surface, on objectui mainb231607:
The pick:packages/react/src/hooks/usePageAssignment.ts. It returns candidates[0] in meta.pages order, and its own comment records that isDefault is not consulted ("Reading it would be new behaviour, not a repair").
The existing layering rule, a candidate to measure: ADR-0048 Phase 2's prefer-local resolution, which prefers the item owned by the current app's package (packages/app-shell/src/utils/preferLocal.ts, used by the console for same-name pages). Also any environment-over-package-over-platform mark the served page items already carry.
The call site:packages/app-shell/src/views/RecordDetailView.tsx (usePageAssignment(objectName)), only if the current app's package must reach the hook. PageAssignmentOptions already declares an app option, "reserved for future use".
Co-located tests, and .changeset/12195-record-page-isdefault.md (@object-ui/reactpatch, plus @object-ui/app-shell only if the call site changes).
⛔ No new option on PageAssignmentOptions, and no other published type change: if one is needed, stop and report. ⛔ If no existing rule decides between two defaults, the renderer invents none: stop and report, and the seat files the objectstack spec card triage named.
(Stop on breach; explain in the report.)
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Responsibility: objectui's record-page assignment (@object-ui/react usePageAssignment), which ignores the declared PageSchema.isDefault and returns the first matching page in GET /meta/page order, so with two default record pages for one object, plugin init order decides | the spec declares isDefault and several shipped pages author it (platform-objects sys-organization/sys-user/sys-position, cloud service-tenant sys-environment/sys-organization); ADR-0048 Phase 2 already prefers the current app's package for same-name metadata | every record page for an object that more than one package ships a default page for; today the Cloud organization page, held only by a cloud test that pins the init order
Thread-read: 6120187277
Serial constraints cleared: read at 2026-10-12T09:45Z on objectui mainb231607:
Duplicates: no remote branch or open PR touches this card's subject. objectui#7298 and objectui#9674 are closed.
{
"issue": 12195,
"status": "needs_decision",
"branch": "claude/issue-12195-record-page-isdefault",
"pr": null,
"session": "session_016djJF12Qt14ejKR5Vjt4sK — subagent run under the seat's session",
"premise_still_valid": true,
"summary": "Stopped at H2 with nothing built, as the triage ruling and H2's stop condition direct. H1 holds on objectui main b231607: the hook returns the first record page in meta.pages order and never reads isDefault. H2, measured on objectstack main 794177d9b: ADR-0048 prefer-local is written for bare NAMES only; the environment layer has a mark (`_provenance`: package | org | env-forced) but the precedence stated over it (ADR-0005) is per (type, name) and already applied server-side, and both cloud pages are `package`; no served-item mark separates platform pages from package pages. So no stated rule decides cloud_organization_detail vs sys_organization_detail, and the objectstack spec card the ruling names is owed (open_questions[0] carries a draft of it). Branch pushed empty as the claim marker; no PR.",
"measurements": {
"H1": "CONFIRMED (objectui main b231607). `usePageAssignment` keeps candidates with `type === 'record'` and a matching `object` and returns `candidates[0]`; its comment records that isDefault is not consulted (\"Reading it would be new behaviour, not a repair\"). Runtime reading, not just source: the pins \"`isDefault` on a later candidate does NOT promote it\" and \"`isDefault: true` on the platform page does not make it win from second place\" in usePageAssignment.declarationOrder-7298.test.tsx pass on main.",
"H2_prefer_local": "Written for bare names only. ADR-0048 section 3.3: \"`getItem`/route resolution resolves a bare name **within the current package first**, keyed on the **package id**\". Section 5, Phase 2 (frontend): \"`preferLocal(list, name, ownerPackageId)` keyed on `_packageId`, wired at the page/dashboard/report/header bare-name sites.\" Nothing in it ranks two differently named pages bound to one object. It would not decide the cloud case even if widened: the organization record page is reached from Setup (setup-nav.contributions.ts, nav item `nav_organization`, objectName `sys_organization`), the Setup app's package is `com.objectstack.setup` (packages/apps/setup, `SETUP_APP_PACKAGE_ID`), and neither candidate belongs to it. Cloud's own app context: NOT MEASURED, reason: objectstack-ai/cloud is not reachable from this session (REST read 403; add_repo refused, no access).",
"H2_environment_layer": "The mark exists: `_provenance` (ADR-0010 section 3.5; `MetadataProvenanceSchema` = package | org | env-forced; the protocol stamps `_provenance: 'org'` server-side on an environment save). The precedence stated over the layers is ADR-0005's read path, \"getMetaItem(type, name) 1. sys_metadata WHERE (type, name, ...) overlay (wins) 2. SchemaRegistry / MetadataService artifact default\": it decides between an overlay and the artifact of the SAME name and is applied before the list is served. Nothing ranks two distinct names by layer; ADR-0010 section 6 Phase 5 defers it outright: \"Multi-layer overlay (base + ISV layer + customer layer) is the only mature-platform feature left unaddressed by this ADR.\" Both cloud candidates are packaged, so this mark would not separate them anyway.",
"H2_platform_vs_package": "No mark. Served page items carry the `MetadataProtectionFields` envelope that PageSchema spreads (`_packageId`, `_packageVersion`, `_provenance`, `_lock*`); `_provenance` is `package` for both platform and app packages. The manifest's `scope` (cloud | system | project) is a deployment scope, is not stamped on served items, and states no precedence.",
"H2_verdict": "No stated rule decides the cloud case (two `package` pages, different names, neither in the current app's package). Per the ruling, the renderer invents none: nothing built.",
"H3": "`PageAssignmentOptions.app` is documented \"Owning app filter (reserved for future use)\": a FILTER keyed on an app. Prefer-local is a PREFERENCE keyed on a package id. Using `app` for it would change its documented meaning (a published type change; Clause-2 would flip). Moot given H2; recorded for the spec card.",
"H4": "Only `RecordDetailView` calls the hook (`usePageAssignment(objectName)`). `nav-target.ts` (`isStaticPageOption`), `PagePreview.tsx` (`isRecordPage`) and `runtime-metadata-persistence.ts` only mirror its `type === 'record'` discriminator in prose and never read its selection, so a new order changes nothing they show. One producer note: `recordPageEnvelope` in runtime-metadata-persistence.ts writes `kind: 'full'` with no isDefault, so once isDefault is honoured a page it mints for an object that also has a packaged default would lose to that default; it has no non-test caller on main today."
},
"tests": "No diff, so nothing of mine to test. Premise reading at b231607 through the verify lock: `pnpm exec vitest run --maxWorkers=2` on usePageAssignment.declarationOrder-7298.test.tsx and usePageAssignment.typeDiscriminator-9674.test.tsx printed 'Test Files 2 passed (2)', 'Tests 17 passed (17)', 'VERDICT command-exit 0'. No ablation: no implementation exists to ablate.",
"gates": "NOT RUN: no diff. Stopped at H2 before the first edit, so none of the hand-derived gates (build closure, type-check, vi-mock-specifiers, test-path-roots, spec-symbols, changeset presence and claims, pending-changeset-literals, control-bytes, new-line-citations) has a change to judge.",
"line_budget": "n/a: no file changed; nothing under skills/ touched.",
"files_changed": [],
"deviations": [
"Stopped before any edit per H2's stop condition: no PR, no changeset, no co-located tests. The triage pins (two isDefault pages resolve the same way whatever the registry order; CONTROL: a single default unchanged) are not delivered.",
"One MCP call outside GitHub: mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read) to measure cloud's app context; refused for lack of access. Not a GitHub write."
],
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/cloud, read; refused). Zero GitHub MCP tools.",
"api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/12195/comments (this os-dev-report, through post-stamped). REST reads: GET issues/12195, GET issues/12195/comments (objectui); GET objectstack-ai/cloud pulls/2786 answered 403. The empty-branch git push is not REST.",
"open_questions": [
{
"question": "Q1 (the objectstack spec card the ruling names). Two or more `isDefault: true` record pages bound to one object, e.g. cloud's `cloud_organization_detail` (com.objectstack.service-tenant) and plugin-auth's `sys_organization_detail` (com.objectstack.plugin-auth), both packaged, opened from Setup (com.objectstack.setup, which owns neither). No stated rule ranks them. Which rule should the spec state?",
"options": [
"A. One default per object per deployment. The spec states that at most one `isDefault: true` record page may exist for an object; a second is refused at registration and at save, naming both holders (precedent: ADR-0048's 2026-10-08 narrowing, which refuses a second position / permission set / capability of one name and names both holders). The renderer then needs no tie-break: isDefault first, declaration order among non-defaults only. Cost: objectstack spec + registry refusal (M); cloud must stop registering a second default, by composing plugin-auth without its organization page or by an environment overlay of `sys_organization_detail` by name (S to M, and it must land before the refusal ships); objectui S. Real cost: an app PACKAGE can no longer override a platform record page by shipping its own default; only host composition or an environment overlay can.",
"B. A precedence ladder among defaults: environment-authored (`_provenance: 'org'`) over packaged; among packaged, the current app's package (ADR-0048 prefer-local widened from names to object-bound record pages); then a rule for pages from packages that are neither (for example dependent over dependency). Cost: L. The hook needs the current app's package id (`PageAssignmentOptions.app` changes from a filter to a preference, or a new option: a published type change, Clause-2 flips), and the dependency relation is not on served items (a new served field). The first two rungs still do not decide the cloud case.",
"C. The server decides: the metadata protocol states and applies the rule and serves one resolved default record page per object, and the renderer reads that. Cost: L, a new protocol surface. C says where the rule lives, not what it is: it still needs A or B."
],
"recommendation": "A. Real need (measured): every object with a default record page in objectstack main 794177d9b has exactly one (`*.page.ts` with `type: 'record'`: sys_organization, sys_position, sys_user, sys_approval_request, three showcase objects); the only multi-default instance on record is cloud's organization page, one host replacing one platform page, which host composition serves. A ladder serves a marketplace of competing defaults that nobody has. Long-term: a declared invariant refused loudly is contract-first, while a ladder has to be implemented and kept in step in the renderer and later the server. AI error: under A an AI that writes a second default is refused with the other holder named; under B it gets a silent winner chosen by a rung it never sees. Startup scope: A adds no key, no option and no served field; B adds a published option change and a served field; C adds a protocol surface."
},
{
"question": "Q2 (sequencing, for the seat). Land step 1 (isDefault pages before non-default ones, registry order as the named last resort) now as a partial PR, or hold it for Q1's rule?",
"options": [
"A. Land step 1 now (Part of the card). A single default wins whatever the order. Two defaults stay on registry order, so the cloud case is unchanged. Three objectui#7298 pins invert, among them \"the app page wins when it is declared first\": an app's own record page for sys_user, sys_organization, sys_position or sys_approval_request without `isDefault: true` stops winning from first place, and adding `isDefault: true` puts it in the undecided tie.",
"B. Hold step 1 and land it with Q1's rule in one PR that meets both triage pins."
],
"recommendation": "B. Step 1 alone cannot meet the pin \"two isDefault record pages for one object resolve the same way whatever the registry order\"; it moves the only override path apps have today (declaration order) into the undecided case; and the step-1 change is a few lines, so holding it costs nothing. Under Q1-A, step 1 plus the refusal is the whole fix."
}
],
"out_of_scope_findings": [
"carrier: this card (objectui#12195) · noted, not filed. objectstack `packages/spec/liveness/page.json` row `isDefault` reads status `live`, note \"default-page selection for an object/page-type — objectui page resolution\", and `view.zod.ts` `NAVIGATION_VIEW_RETIRED` tells authors to \"assign a `record` page to the object and let `isDefault` pick the one that opens\", while objectui's resolver never reads the key (H1). Same seam as this card (spec:PageSchema.isDefault to renderer:usePageAssignment), so it closes when the card does. dedupe words: isDefault liveness, page.json live, NAVIGATION_VIEW_RETIRED isDefault"
]
}
Both halves of the restart line are met:npm view @objectstack/spec@next version names 18.0.0-next.1, which carries objectstack 5f37d36ce3 (an ancestor of tag commit 0c52e43075), and objectui resolves it.
The grade is unchanged: p3. The assignee from the earlier claim stays; the dispatching seat confirms or releases it. It is listed on objectui#12334.
Branch state:claude/issue-12195-record-page-isdefault has no commits of its own. REST compare reads it 0 ahead and 96 behind main, and the dev stopped at H2 with nothing built (6121900317). There is no Resume-from:, so the next claim starts from main.
What carries over:
the dev's H1 reading and H2 measurement in 6121900317;
Claim: PM loop round 1
Session: session_01JZnppgHRp4sREcouhkQYDB
Account: sunhaolin
Branch: claude/issue-12195-record-page-isdefault
Worktree: objectui-issue-12195
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/react/src/hooks/usePageAssignment.ts (the pick and its comment), its tests under packages/react/src/hooks/__tests__/ (the objectui#7298 declaration-order pins the ruling inverts, plus new pins), and one .changeset/12195-*.md (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
Clause-②: no
Responsibility: objectui's record-page pick (@object-ui/reactusePageAssignment), which returns the first matching record page in served order and never reads the declared PageSchema.isDefault | the spec now refuses a second default record page for one object at every door (ruling 6134056653, letter A, on objectstack-ai/objectstack#23015, landed as 5f37d36ce3 and installed in 18.0.0-next.1), so honouring isDefault needs no tie-break | every object served with a default record page beside another record page; on objectstack main, sys_user, sys_organization, sys_position, sys_approval_request and three showcase objects declare one default each
Thread-read: 6181117071
Serial constraints cleared: none. Read at objectui mainfc528c3eb: no open PR and no in-flight claim names usePageAssignment.ts or its tests. The branch is reused from the released claim 6121762147; it has no commits of its own and starts from main, since the release 6181117071 carries no Resume-from:. objectui#12045 (claimed alongside by this seat) mounts the served sys_approval_request_detail page, the only record page for its object, so either pick order selects it.
Ruling carried: "The renderer then honours isDefault with no tie-break." Among non-default pages, served order stays the last resort.
Accept-set reading: no type, option or export changes. The hook follows a key the spec already declares.
{
"issue": 12195,
"status": "done",
"branch": "claude/issue-12195-record-page-isdefault",
"pr": "https://github.com/objectstack-ai/objectui/pull/12431",
"session": "session_01JZnppgHRp4sREcouhkQYDB — subagent run under the domain:ui seat 1 session (claim 6184497895)",
"premise_still_valid": true,
"summary": "usePageAssignment now returns the object's default record page: after the existing type/object/matchesAssignment filter it returns the candidate the spec reads as the object's default, else candidates[0]; an explicit pageName still wins first. Route refinement over the PM's suggestion (same behaviour): the default is read through @objectstack/spec/ui defaultRecordPageObject, the one reading every refusing door asks (inside the candidate set it is exactly isDefault === true); @objectstack/spec was already a runtime dependency and SchemaRenderer.tsx already imports values from @objectstack/spec/ui, so no dependency, option, key, type or export changes (Clause-② no holds). The hook comment now states the rule and its source (ruling A, record 6134056653; the spec's one-default refusal at defineStack/composeStacks, registration and save) and the cold-boot boundary (objectstack#23213, closed not_planned) for which no rung is invented: the first default served wins. Three objectui#7298 pins rewritten to the ruled behaviour naming ruling A (none deleted); new suite usePageAssignment.isDefault-12195.test.tsx carries the PM's four pins plus controls; changeset @object-ui/react patch naming who sees a different page and the two override paths ruling A keeps. Branch: the remote branch sat at b231607 with 0 commits of its own (merge-base --is-ancestor b231607 origin/main: exit 0, self-proving), reset to origin/main 7c13d30527 (main had moved past fc528c3eb; git diff fc528c3eb 7c13d30527 over packages/react/src/hooks is empty) and fast-forwarded by push. The previous dev's carried finding (liveness row and NAVIGATION_VIEW_RETIRED telling authors to let isDefault pick the page) becomes true with this change, as does packages/plugin-gantt/README.md's same sentence. Worktree removed (node_modules first, no --force); temporary namespaced ref deleted.",
"measurements": {
"A1": "HOLDS at 7c13d30527: filter to type === 'record', matching object, matchesAssignment, then candidates[0]; comment recorded isDefault not consulted. Change placed after the filter; pageName branch untouched.",
"A2": "HOLDS with one correction. The three named pins are rewritten (the third now also carries a same-list control with the flag removed, so the flag is the variable). The two pure-order pins stay as written apart from names that said position is the whole rule (now: among non-defaults). Correction: the pin 'the platform page wins when IT is declared first' is not an order-among-non-defaults pin (that page is the default); its assertion stays true but its old name ('nothing authored breaks the tie') became false and is renamed.",
"A3": "HOLDS. The spec module default-record-page.ts says the renderer opens the default 'with no tie-break between two defaults: registration order, package order and declaration order decide nothing'; objectql's default-record-page.ts states the cold-boot boundary (stored default saved while no package held one, then a package adding one, both reach the registry unrefused on restart; a hot install is refused). No ordinary path delivering two defaults found, so no open_question. A pin holds 'no tie-break is invented, served order decides' for that pair.",
"A4": "0. objectstack origin/main f3fd209b3: record pages in non-test source are six, each isDefault true and alone for its object (sys_user_detail, sys_organization_detail, sys_approval_request_detail, showcase_project_detail, showcase_task_detail, showcase_settings for showcase_preference); sys_position_detail retired with sys_position; non-default competitors 0; objectstack's own server tests do not feed this hook. objectui 7c13d30527: no app/example/doc fixture serves a record page into the hook beside a default; 16 of the 58 RecordDetailView suites serve a type 'record' page from a single literal each, one (recordOpenRequests-11699) flags it isDefault alone; competitors 0.",
"A5": "HOLDS. recordPageEnvelope still mints a kind 'full' record page with no isDefault; its only references are runtime-metadata-persistence.test.ts and ADR-0034 prose (no non-test caller). In PR Acceptance notes, not in the diff."
},
"tests": "All at e72618f400 (the final commit). Build closure: pnpm --workspace-concurrency=2 --filter '@object-ui/react^...' build: BUILD_EXIT=0. pnpm --filter @object-ui/react type-check (script name echoed; tsc --noEmit && tsc -p tsconfig.test.json): TYPECHECK_EXIT=0; tsc -p tsconfig.test.json --listFiles lists all three usePageAssignment.*.test.tsx. The three usePageAssignment suites (pnpm exec vitest run --maxWorkers=2, paths from repo root): 'Test Files 3 passed (3)', 'Tests 27 passed (27)'. Whole package, vitest run packages/react/: 'Test Files 111 passed (111)', 'Tests 1420 passed (1420)'. All 58 packages/app-shell RecordDetailView* suites (the hook's only caller; @object-ui/react aliased to packages/react/src in vitest.config.mts), two batches of 29: 'Test Files 29 passed (29)' 'Tests 210 passed (210)' and 'Test Files 29 passed (29)' 'Tests 168 passed (168)'; the 16 serving a record page are named in the PR body. pnpm --filter @object-ui/react build: exit 0. Ablation (fix committed first; objectstack's scripts/ablation-replace.mjs run with cwd in the worktree, WRAP mode, plus a shell trap restoring git checkout HEAD on the absolute path): anchor (the return line that finds the candidate defaultRecordPageObject names, falling back to candidates[0]) x1 to x0, replacement 'return candidates[0]; /* ABLATION-12195 */', marker ABLATION-12195 x0 to x1 on disk (grep -c 1 during the run), blob 685e6ad301c1 to fff84703902a; the 7298 + 12195 suites: 'Tests 7 failed | 13 passed (20)', vitest exit 1. Direction: red, as predicted (3 rewritten 7298 pins + 4 new rule pins red; controls, pageName pin and after-the-filter pin green as expected under this mutation). Restore proven: blob after restore 685e6ad301c1 == HEAD blob, git diff HEAD empty, marker 0, anchor 1. No dist in the path (suites import the hook by relative source path).",
"gates": "Local subset, derived by hand from objectui package.json check:* and lint.yml / ci.yml / changeset-guard.yml / changeset-presence.yml (objectstack's dispatch-gates refuses a sister repo; no --ran reconciliation exists here), exit codes written to files before any pipe, at e72618f400: check-changeset-presence 0 ('3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-no-major 0; check-changeset-fixed 0; check-changeset-overwrite 0; check:changeset-claims 0 ('No pending changeset names a file this change touches'); check:pending-changeset-literals 0; check:control-bytes 0 ('OK (scanned 8768 tracked text file(s)'); check:new-line-citations 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check:spec-symbols 0; check:test-path-roots 0; check:vi-mock-specifiers 0; check-vi-mock-override-shape 0; check:phantom-deps 0; check:esm-specifiers 0; check:unreferenced-sources 0; eslint on the 3 changed TS files 0 (0 errors, 10 warnings all on pre-existing lines); check-governed-queue-guard --test on the 4 paths: NOT GOVERNED. check:spec-floors exit 1 = prerequisite, not a red: first run 19 findings all no-artifact (unbuilt workspace); after building @object-ui/react its row left the findings (judged clean against ^18.0.0-next.1, the package that imports defaultRecordPageObject), 18 remaining all no-artifact for packages this diff does not touch: NOT MEASURED, reason unbuilt workspace. spec-range-floors.yml does not run on PRs (schedule, workflow_dispatch, push to main on its own script), so the local react reading is the evidence for this PR. PR check-runs on e72618f400 at report time (36 of total_count 36): success: Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, label; skipped: Test (coverage), the coverage shard matrix row, dependabot; in_progress: Build & E2E, Build Docs, Bundle Analysis, Lint, Live E2E (informational), Repo Gates, Spec Main Shape Gate, Test (dist pins), Test (shard 1/16 to 16/16), Type Check.",
"line_budget": "n/a: nothing under skills/ touched.",
"files_changed": [
"packages/react/src/hooks/usePageAssignment.ts",
"packages/react/src/hooks/__tests__/usePageAssignment.declarationOrder-7298.test.tsx",
"packages/react/src/hooks/__tests__/usePageAssignment.isDefault-12195.test.tsx",
".changeset/12195-record-page-isdefault.md"
],
"deviations": [
"PR body correction the seat may write (dev writes the body once, no PATCH): the Gates bullet on check:spec-floors ends 'NOT MEASURED locally, left to CI's spec-range-floors.yml'. That workflow does not run on pull requests (schedule, workflow_dispatch, push to main on its own script). Suggested replacement for that clause: 'NOT MEASURED locally; spec-range-floors.yml runs on a schedule on main, not on this PR, so the react row judged here is this PR's evidence.'",
"Base was origin/main 7c13d30527, not the dispatch's fc528c3eb: main moved before the worktree was cut; the hook and its tests are byte-identical between the two (git diff over packages/react/src/hooks empty; fc528c3eb is an ancestor of 7c13d30527, exit 0)."
],
"mcp_calls": "0 — no MCP tool of any kind called.",
"api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (204) executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create via scripts/pm/fleet-write/dispatch.mjs (run 38685536879, success) = POST /repos/objectstack-ai/objectui/pulls (draft, PR 12431; body read back 11298 bytes sent, 11298 stored, identical) + POST /repos/objectstack-ai/objectui/issues/12431/assignees (sunhaolin, read back); (2) this os-dev-report via scripts/pm/post-stamped.mjs = POST /repos/objectstack-ai/objectui/issues/12195/comments. No label writes (the PR's 'package: react' and 'tests' labels are labeler.yml's, not mine). git push x2 of the branch (not REST). REST reads: GET objectui issues/12195, issues/12195/comments, pulls/12431, commits/e72618f400/check-runs (x2); GET objectstack issues/comments/6134056653 and 6143275292.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:ui seat 1 (amend this PR's file surface, or leave it) · noted, not filed. scripts/page-key-read-census.mjs header boundary 4 says 'isDefault is declared and nothing consults it'; this change makes that sentence false. Script prose, not published, no gate reads it; outside the claim's file surface and editing it adds that script's own suite (scripts/__tests__/page-key-read-census.test.ts) as a verification surface, so not touched. Also in the PR's Acceptance notes."
]
}
With no default, declaration order holds, and an explicit pageName still wins.
The spec's liveness row for isDefault ("default-page selection … objectui page resolution") and NAVIGATION_VIEW_RETIRED's "let isDefault pick the one that opens" are now true in objectui.
The chain: claim 6184497895, dev report on this card, ACCEPT 6185021939. No contract face applied, so no contract review was owed.
Recorded at landing: attempt 1 of the advisory Spec Main Shape Gate was red with objectui#12422's build-order race (6185106215). Its one re-run passed.
Label:pm:dispatched is removed in the same act as this note. The assignee stays as the record of who drove the card.
Activity
objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·priority:p3·domain:ui·area:records, intopm:queue. Honour the declaredisDefault; the tie-break comes from an existing layering rule, not a new oneTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T08:00Z. ⛔ Not a claim, ⛔ not a dispatch.usePageAssignmentprefersisDefaultrecord pages. ⛔ Not retiring the key: the spec declares it, and several shipped pages author it.isDefaultpages: the claim reads the platform's existing metadata layering (environment over package over platform) and applies it.isDefaultrecord pages for one object resolve the same way whatever the registry order. CONTROL: a single default is unchanged.Clause-②: no.objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 7 (as triage
6120187277directs:usePageAssignmenthonours the declaredisDefault, and the tie-break between several defaults comes from the platform's existing layering, ⛔ not a rule the renderer invents)Session:
session_016djJF12Qt14ejKR5Vjt4sKAccount:
os-steveBranch:
claude/issue-12195-record-page-isdefaultWorktree:
objectui-issue-12195Domain:
domain:uiSeat:
domain:ui#1File surface, on objectui
mainb231607:packages/react/src/hooks/usePageAssignment.ts. It returnscandidates[0]inmeta.pagesorder, and its own comment records thatisDefaultis not consulted ("Reading it would be new behaviour, not a repair").packages/app-shell/src/utils/preferLocal.ts, used by the console for same-name pages). Also any environment-over-package-over-platform mark the served page items already carry.packages/app-shell/src/views/RecordDetailView.tsx(usePageAssignment(objectName)), only if the current app's package must reach the hook.PageAssignmentOptionsalready declares anappoption, "reserved for future use"..changeset/12195-record-page-isdefault.md(@object-ui/reactpatch, plus@object-ui/app-shellonly if the call site changes).PageAssignmentOptions, and no other published type change: if one is needed, stop and report. ⛔ If no existing rule decides between two defaults, the renderer invents none: stop and report, and the seat files the objectstack spec card triage named.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Responsibility:
objectui's record-page assignment (@object-ui/react usePageAssignment), which ignores the declared PageSchema.isDefault and returns the first matching page in GET /meta/page order, so with two default record pages for one object, plugin init order decides | the spec declares isDefault and several shipped pages author it (platform-objects sys-organization/sys-user/sys-position, cloud service-tenant sys-environment/sys-organization); ADR-0048 Phase 2 already prefers the current app's package for same-name metadata | every record page for an object that more than one package ships a default page for; today the Cloud organization page, held only by a cloud test that pins the init orderThread-read: 6120187277
Serial constraints cleared: read at 2026-10-12T09:45Z on objectui
mainb231607:PageView.tsx,vite.config.ts) and PRs objectui#12216, fix(plugin-form): an Edit form saves the owner the user changed or cleared, and still never echoes an untouched one #12218 and fix(fields): the position-ref grant picker stops offering the audience anchors (objectui#12200) #12219 in the queue. None touchesusePageAssignmentorRecordDetailView.tsx.ManagedByBadgeand the top bar. objectui#12194 renders inside the record page: read its PR before touchingRecordDetailView.tsx.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 12195, "status": "needs_decision", "branch": "claude/issue-12195-record-page-isdefault", "pr": null, "session": "session_016djJF12Qt14ejKR5Vjt4sK — subagent run under the seat's session", "premise_still_valid": true, "summary": "Stopped at H2 with nothing built, as the triage ruling and H2's stop condition direct. H1 holds on objectui main b231607: the hook returns the first record page in meta.pages order and never reads isDefault. H2, measured on objectstack main 794177d9b: ADR-0048 prefer-local is written for bare NAMES only; the environment layer has a mark (`_provenance`: package | org | env-forced) but the precedence stated over it (ADR-0005) is per (type, name) and already applied server-side, and both cloud pages are `package`; no served-item mark separates platform pages from package pages. So no stated rule decides cloud_organization_detail vs sys_organization_detail, and the objectstack spec card the ruling names is owed (open_questions[0] carries a draft of it). Branch pushed empty as the claim marker; no PR.", "measurements": { "H1": "CONFIRMED (objectui main b231607). `usePageAssignment` keeps candidates with `type === 'record'` and a matching `object` and returns `candidates[0]`; its comment records that isDefault is not consulted (\"Reading it would be new behaviour, not a repair\"). Runtime reading, not just source: the pins \"`isDefault` on a later candidate does NOT promote it\" and \"`isDefault: true` on the platform page does not make it win from second place\" in usePageAssignment.declarationOrder-7298.test.tsx pass on main.", "H2_prefer_local": "Written for bare names only. ADR-0048 section 3.3: \"`getItem`/route resolution resolves a bare name **within the current package first**, keyed on the **package id**\". Section 5, Phase 2 (frontend): \"`preferLocal(list, name, ownerPackageId)` keyed on `_packageId`, wired at the page/dashboard/report/header bare-name sites.\" Nothing in it ranks two differently named pages bound to one object. It would not decide the cloud case even if widened: the organization record page is reached from Setup (setup-nav.contributions.ts, nav item `nav_organization`, objectName `sys_organization`), the Setup app's package is `com.objectstack.setup` (packages/apps/setup, `SETUP_APP_PACKAGE_ID`), and neither candidate belongs to it. Cloud's own app context: NOT MEASURED, reason: objectstack-ai/cloud is not reachable from this session (REST read 403; add_repo refused, no access).", "H2_environment_layer": "The mark exists: `_provenance` (ADR-0010 section 3.5; `MetadataProvenanceSchema` = package | org | env-forced; the protocol stamps `_provenance: 'org'` server-side on an environment save). The precedence stated over the layers is ADR-0005's read path, \"getMetaItem(type, name) 1. sys_metadata WHERE (type, name, ...) overlay (wins) 2. SchemaRegistry / MetadataService artifact default\": it decides between an overlay and the artifact of the SAME name and is applied before the list is served. Nothing ranks two distinct names by layer; ADR-0010 section 6 Phase 5 defers it outright: \"Multi-layer overlay (base + ISV layer + customer layer) is the only mature-platform feature left unaddressed by this ADR.\" Both cloud candidates are packaged, so this mark would not separate them anyway.", "H2_platform_vs_package": "No mark. Served page items carry the `MetadataProtectionFields` envelope that PageSchema spreads (`_packageId`, `_packageVersion`, `_provenance`, `_lock*`); `_provenance` is `package` for both platform and app packages. The manifest's `scope` (cloud | system | project) is a deployment scope, is not stamped on served items, and states no precedence.", "H2_verdict": "No stated rule decides the cloud case (two `package` pages, different names, neither in the current app's package). Per the ruling, the renderer invents none: nothing built.", "H3": "`PageAssignmentOptions.app` is documented \"Owning app filter (reserved for future use)\": a FILTER keyed on an app. Prefer-local is a PREFERENCE keyed on a package id. Using `app` for it would change its documented meaning (a published type change; Clause-2 would flip). Moot given H2; recorded for the spec card.", "H4": "Only `RecordDetailView` calls the hook (`usePageAssignment(objectName)`). `nav-target.ts` (`isStaticPageOption`), `PagePreview.tsx` (`isRecordPage`) and `runtime-metadata-persistence.ts` only mirror its `type === 'record'` discriminator in prose and never read its selection, so a new order changes nothing they show. One producer note: `recordPageEnvelope` in runtime-metadata-persistence.ts writes `kind: 'full'` with no isDefault, so once isDefault is honoured a page it mints for an object that also has a packaged default would lose to that default; it has no non-test caller on main today." }, "tests": "No diff, so nothing of mine to test. Premise reading at b231607 through the verify lock: `pnpm exec vitest run --maxWorkers=2` on usePageAssignment.declarationOrder-7298.test.tsx and usePageAssignment.typeDiscriminator-9674.test.tsx printed 'Test Files 2 passed (2)', 'Tests 17 passed (17)', 'VERDICT command-exit 0'. No ablation: no implementation exists to ablate.", "gates": "NOT RUN: no diff. Stopped at H2 before the first edit, so none of the hand-derived gates (build closure, type-check, vi-mock-specifiers, test-path-roots, spec-symbols, changeset presence and claims, pending-changeset-literals, control-bytes, new-line-citations) has a change to judge.", "line_budget": "n/a: no file changed; nothing under skills/ touched.", "files_changed": [], "deviations": [ "Stopped before any edit per H2's stop condition: no PR, no changeset, no co-located tests. The triage pins (two isDefault pages resolve the same way whatever the registry order; CONTROL: a single default unchanged) are not delivered.", "One MCP call outside GitHub: mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read) to measure cloud's app context; refused for lack of access. Not a GitHub write." ], "mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/cloud, read; refused). Zero GitHub MCP tools.", "api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/12195/comments (this os-dev-report, through post-stamped). REST reads: GET issues/12195, GET issues/12195/comments (objectui); GET objectstack-ai/cloud pulls/2786 answered 403. The empty-branch git push is not REST.", "open_questions": [ { "question": "Q1 (the objectstack spec card the ruling names). Two or more `isDefault: true` record pages bound to one object, e.g. cloud's `cloud_organization_detail` (com.objectstack.service-tenant) and plugin-auth's `sys_organization_detail` (com.objectstack.plugin-auth), both packaged, opened from Setup (com.objectstack.setup, which owns neither). No stated rule ranks them. Which rule should the spec state?", "options": [ "A. One default per object per deployment. The spec states that at most one `isDefault: true` record page may exist for an object; a second is refused at registration and at save, naming both holders (precedent: ADR-0048's 2026-10-08 narrowing, which refuses a second position / permission set / capability of one name and names both holders). The renderer then needs no tie-break: isDefault first, declaration order among non-defaults only. Cost: objectstack spec + registry refusal (M); cloud must stop registering a second default, by composing plugin-auth without its organization page or by an environment overlay of `sys_organization_detail` by name (S to M, and it must land before the refusal ships); objectui S. Real cost: an app PACKAGE can no longer override a platform record page by shipping its own default; only host composition or an environment overlay can.", "B. A precedence ladder among defaults: environment-authored (`_provenance: 'org'`) over packaged; among packaged, the current app's package (ADR-0048 prefer-local widened from names to object-bound record pages); then a rule for pages from packages that are neither (for example dependent over dependency). Cost: L. The hook needs the current app's package id (`PageAssignmentOptions.app` changes from a filter to a preference, or a new option: a published type change, Clause-2 flips), and the dependency relation is not on served items (a new served field). The first two rungs still do not decide the cloud case.", "C. The server decides: the metadata protocol states and applies the rule and serves one resolved default record page per object, and the renderer reads that. Cost: L, a new protocol surface. C says where the rule lives, not what it is: it still needs A or B." ], "recommendation": "A. Real need (measured): every object with a default record page in objectstack main 794177d9b has exactly one (`*.page.ts` with `type: 'record'`: sys_organization, sys_position, sys_user, sys_approval_request, three showcase objects); the only multi-default instance on record is cloud's organization page, one host replacing one platform page, which host composition serves. A ladder serves a marketplace of competing defaults that nobody has. Long-term: a declared invariant refused loudly is contract-first, while a ladder has to be implemented and kept in step in the renderer and later the server. AI error: under A an AI that writes a second default is refused with the other holder named; under B it gets a silent winner chosen by a rung it never sees. Startup scope: A adds no key, no option and no served field; B adds a published option change and a served field; C adds a protocol surface." }, { "question": "Q2 (sequencing, for the seat). Land step 1 (isDefault pages before non-default ones, registry order as the named last resort) now as a partial PR, or hold it for Q1's rule?", "options": [ "A. Land step 1 now (Part of the card). A single default wins whatever the order. Two defaults stay on registry order, so the cloud case is unchanged. Three objectui#7298 pins invert, among them \"the app page wins when it is declared first\": an app's own record page for sys_user, sys_organization, sys_position or sys_approval_request without `isDefault: true` stops winning from first place, and adding `isDefault: true` puts it in the undecided tie.", "B. Hold step 1 and land it with Q1's rule in one PR that meets both triage pins." ], "recommendation": "B. Step 1 alone cannot meet the pin \"two isDefault record pages for one object resolve the same way whatever the registry order\"; it moves the only override path apps have today (declaration order) into the undecided case; and the step-1 change is a few lines, so holding it costs nothing. Under Q1-A, step 1 plus the refusal is the whole fix." } ], "out_of_scope_findings": [ "carrier: this card (objectui#12195) · noted, not filed. objectstack `packages/spec/liveness/page.json` row `isDefault` reads status `live`, note \"default-page selection for an object/page-type — objectui page resolution\", and `view.zod.ts` `NAVIGATION_VIEW_RETIRED` tells authors to \"assign a `record` page to the object and let `isDefault` pick the one that opens\", while objectui's resolver never reads the key (H1). Same seam as this card (spec:PageSchema.isDefault to renderer:usePageAssignment), so it closes when the card does. dedupe words: isDefault liveness, page.json live, NAVIGATION_VIEW_RETIRED isDefault" ] }Generated by Claude Code
2 remaining items
objectstack-fleet commentedon Oct 14, 2026
Unlock scan:
pm:on-hold→pm:queue· objectui now resolves18.0.0-next.1· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T20:09Zmainnow pins@objectstack/spec^18.0.0-next.1, and its lockfile resolves18.0.0-next.1.npm view @objectstack/spec@next versionnames18.0.0-next.1, which carries objectstack5f37d36ce3(an ancestor of tag commit0c52e43075), and objectui resolves it.objectstack-fleet commentedon Oct 14, 2026
Release: claim
6121762147(session_016djJF12Qt14ejKR5Vjt4sK, accountos-steve,Seat: domain:ui#1) · released bydomain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-14T23:55Zsession_01AavokzJ5DndAwitDXvKy4U).6177349667.reset()cannot name a package — after objectstack#23165 a console reset or discard of an item two packages hold answers 409, and the remedy?package=is one the console cannot send #12335 and ci: on 2026-10-14 fiveTest (shard N/12)jobs were cancelled at the 20-minute job limit while still passing tests, though their sibling shards finish in 7–13 minutes, so a green PR reads red until a re-run #12384), so it cannot confirm the claim by dispatching now.claude/issue-12195-record-page-isdefaulthas no commits of its own. RESTcomparereads it 0 ahead and 96 behindmain, and the dev stopped at H2 with nothing built (6121900317). There is noResume-from:, so the next claim starts frommain.6121900317;18.0.0-next.1(6177349667).pm:queuewith no assignee, the shape a queue card has. Anydomain:uiseat may claim it in order.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1
Session:
session_01JZnppgHRp4sREcouhkQYDBAccount:
sunhaolinBranch:
claude/issue-12195-record-page-isdefaultWorktree:
objectui-issue-12195Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/react/src/hooks/usePageAssignment.ts(the pick and its comment), its tests underpackages/react/src/hooks/__tests__/(the objectui#7298 declaration-order pins the ruling inverts, plus new pins), and one.changeset/12195-*.md(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuinames no path-derived mandate)Clause-②: no
Responsibility: objectui's record-page pick (
@object-ui/reactusePageAssignment), which returns the first matching record page in served order and never reads the declaredPageSchema.isDefault| the spec now refuses a second default record page for one object at every door (ruling6134056653, letter A, on objectstack-ai/objectstack#23015, landed as5f37d36ce3and installed in18.0.0-next.1), so honouringisDefaultneeds no tie-break | every object served with a default record page beside another record page; on objectstackmain,sys_user,sys_organization,sys_position,sys_approval_requestand three showcase objects declare one default eachThread-read: 6181117071
Serial constraints cleared: none. Read at objectui
mainfc528c3eb: no open PR and no in-flight claim namesusePageAssignment.tsor its tests. The branch is reused from the released claim6121762147; it has no commits of its own and starts frommain, since the release6181117071carries noResume-from:. objectui#12045 (claimed alongside by this seat) mounts the servedsys_approval_request_detailpage, the only record page for its object, so either pick order selects it.isDefaultwith no tie-break." Among non-default pages, served order stays the last resort.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 12195, "status": "done", "branch": "claude/issue-12195-record-page-isdefault", "pr": "https://github.com/objectstack-ai/objectui/pull/12431", "session": "session_01JZnppgHRp4sREcouhkQYDB — subagent run under the domain:ui seat 1 session (claim 6184497895)", "premise_still_valid": true, "summary": "usePageAssignment now returns the object's default record page: after the existing type/object/matchesAssignment filter it returns the candidate the spec reads as the object's default, else candidates[0]; an explicit pageName still wins first. Route refinement over the PM's suggestion (same behaviour): the default is read through @objectstack/spec/ui defaultRecordPageObject, the one reading every refusing door asks (inside the candidate set it is exactly isDefault === true); @objectstack/spec was already a runtime dependency and SchemaRenderer.tsx already imports values from @objectstack/spec/ui, so no dependency, option, key, type or export changes (Clause-② no holds). The hook comment now states the rule and its source (ruling A, record 6134056653; the spec's one-default refusal at defineStack/composeStacks, registration and save) and the cold-boot boundary (objectstack#23213, closed not_planned) for which no rung is invented: the first default served wins. Three objectui#7298 pins rewritten to the ruled behaviour naming ruling A (none deleted); new suite usePageAssignment.isDefault-12195.test.tsx carries the PM's four pins plus controls; changeset @object-ui/react patch naming who sees a different page and the two override paths ruling A keeps. Branch: the remote branch sat at b231607 with 0 commits of its own (merge-base --is-ancestor b231607 origin/main: exit 0, self-proving), reset to origin/main 7c13d30527 (main had moved past fc528c3eb; git diff fc528c3eb 7c13d30527 over packages/react/src/hooks is empty) and fast-forwarded by push. The previous dev's carried finding (liveness row and NAVIGATION_VIEW_RETIRED telling authors to let isDefault pick the page) becomes true with this change, as does packages/plugin-gantt/README.md's same sentence. Worktree removed (node_modules first, no --force); temporary namespaced ref deleted.", "measurements": { "A1": "HOLDS at 7c13d30527: filter to type === 'record', matching object, matchesAssignment, then candidates[0]; comment recorded isDefault not consulted. Change placed after the filter; pageName branch untouched.", "A2": "HOLDS with one correction. The three named pins are rewritten (the third now also carries a same-list control with the flag removed, so the flag is the variable). The two pure-order pins stay as written apart from names that said position is the whole rule (now: among non-defaults). Correction: the pin 'the platform page wins when IT is declared first' is not an order-among-non-defaults pin (that page is the default); its assertion stays true but its old name ('nothing authored breaks the tie') became false and is renamed.", "A3": "HOLDS. The spec module default-record-page.ts says the renderer opens the default 'with no tie-break between two defaults: registration order, package order and declaration order decide nothing'; objectql's default-record-page.ts states the cold-boot boundary (stored default saved while no package held one, then a package adding one, both reach the registry unrefused on restart; a hot install is refused). No ordinary path delivering two defaults found, so no open_question. A pin holds 'no tie-break is invented, served order decides' for that pair.", "A4": "0. objectstack origin/main f3fd209b3: record pages in non-test source are six, each isDefault true and alone for its object (sys_user_detail, sys_organization_detail, sys_approval_request_detail, showcase_project_detail, showcase_task_detail, showcase_settings for showcase_preference); sys_position_detail retired with sys_position; non-default competitors 0; objectstack's own server tests do not feed this hook. objectui 7c13d30527: no app/example/doc fixture serves a record page into the hook beside a default; 16 of the 58 RecordDetailView suites serve a type 'record' page from a single literal each, one (recordOpenRequests-11699) flags it isDefault alone; competitors 0.", "A5": "HOLDS. recordPageEnvelope still mints a kind 'full' record page with no isDefault; its only references are runtime-metadata-persistence.test.ts and ADR-0034 prose (no non-test caller). In PR Acceptance notes, not in the diff." }, "tests": "All at e72618f400 (the final commit). Build closure: pnpm --workspace-concurrency=2 --filter '@object-ui/react^...' build: BUILD_EXIT=0. pnpm --filter @object-ui/react type-check (script name echoed; tsc --noEmit && tsc -p tsconfig.test.json): TYPECHECK_EXIT=0; tsc -p tsconfig.test.json --listFiles lists all three usePageAssignment.*.test.tsx. The three usePageAssignment suites (pnpm exec vitest run --maxWorkers=2, paths from repo root): 'Test Files 3 passed (3)', 'Tests 27 passed (27)'. Whole package, vitest run packages/react/: 'Test Files 111 passed (111)', 'Tests 1420 passed (1420)'. All 58 packages/app-shell RecordDetailView* suites (the hook's only caller; @object-ui/react aliased to packages/react/src in vitest.config.mts), two batches of 29: 'Test Files 29 passed (29)' 'Tests 210 passed (210)' and 'Test Files 29 passed (29)' 'Tests 168 passed (168)'; the 16 serving a record page are named in the PR body. pnpm --filter @object-ui/react build: exit 0. Ablation (fix committed first; objectstack's scripts/ablation-replace.mjs run with cwd in the worktree, WRAP mode, plus a shell trap restoring git checkout HEAD on the absolute path): anchor (the return line that finds the candidate defaultRecordPageObject names, falling back to candidates[0]) x1 to x0, replacement 'return candidates[0]; /* ABLATION-12195 */', marker ABLATION-12195 x0 to x1 on disk (grep -c 1 during the run), blob 685e6ad301c1 to fff84703902a; the 7298 + 12195 suites: 'Tests 7 failed | 13 passed (20)', vitest exit 1. Direction: red, as predicted (3 rewritten 7298 pins + 4 new rule pins red; controls, pageName pin and after-the-filter pin green as expected under this mutation). Restore proven: blob after restore 685e6ad301c1 == HEAD blob, git diff HEAD empty, marker 0, anchor 1. No dist in the path (suites import the hook by relative source path).", "gates": "Local subset, derived by hand from objectui package.json check:* and lint.yml / ci.yml / changeset-guard.yml / changeset-presence.yml (objectstack's dispatch-gates refuses a sister repo; no --ran reconciliation exists here), exit codes written to files before any pipe, at e72618f400: check-changeset-presence 0 ('3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-no-major 0; check-changeset-fixed 0; check-changeset-overwrite 0; check:changeset-claims 0 ('No pending changeset names a file this change touches'); check:pending-changeset-literals 0; check:control-bytes 0 ('OK (scanned 8768 tracked text file(s)'); check:new-line-citations 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check:spec-symbols 0; check:test-path-roots 0; check:vi-mock-specifiers 0; check-vi-mock-override-shape 0; check:phantom-deps 0; check:esm-specifiers 0; check:unreferenced-sources 0; eslint on the 3 changed TS files 0 (0 errors, 10 warnings all on pre-existing lines); check-governed-queue-guard --test on the 4 paths: NOT GOVERNED. check:spec-floors exit 1 = prerequisite, not a red: first run 19 findings all no-artifact (unbuilt workspace); after building @object-ui/react its row left the findings (judged clean against ^18.0.0-next.1, the package that imports defaultRecordPageObject), 18 remaining all no-artifact for packages this diff does not touch: NOT MEASURED, reason unbuilt workspace. spec-range-floors.yml does not run on PRs (schedule, workflow_dispatch, push to main on its own script), so the local react reading is the evidence for this PR. PR check-runs on e72618f400 at report time (36 of total_count 36): success: Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, label; skipped: Test (coverage), the coverage shard matrix row, dependabot; in_progress: Build & E2E, Build Docs, Bundle Analysis, Lint, Live E2E (informational), Repo Gates, Spec Main Shape Gate, Test (dist pins), Test (shard 1/16 to 16/16), Type Check.", "line_budget": "n/a: nothing under skills/ touched.", "files_changed": [ "packages/react/src/hooks/usePageAssignment.ts", "packages/react/src/hooks/__tests__/usePageAssignment.declarationOrder-7298.test.tsx", "packages/react/src/hooks/__tests__/usePageAssignment.isDefault-12195.test.tsx", ".changeset/12195-record-page-isdefault.md" ], "deviations": [ "PR body correction the seat may write (dev writes the body once, no PATCH): the Gates bullet on check:spec-floors ends 'NOT MEASURED locally, left to CI's spec-range-floors.yml'. That workflow does not run on pull requests (schedule, workflow_dispatch, push to main on its own script). Suggested replacement for that clause: 'NOT MEASURED locally; spec-range-floors.yml runs on a schedule on main, not on this PR, so the react row judged here is this PR's evidence.'", "Base was origin/main 7c13d30527, not the dispatch's fc528c3eb: main moved before the worktree was cut; the hook and its tests are byte-identical between the two (git diff over packages/react/src/hooks empty; fc528c3eb is an ancestor of 7c13d30527, exit 0)." ], "mcp_calls": "0 — no MCP tool of any kind called.", "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (204) executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create via scripts/pm/fleet-write/dispatch.mjs (run 38685536879, success) = POST /repos/objectstack-ai/objectui/pulls (draft, PR 12431; body read back 11298 bytes sent, 11298 stored, identical) + POST /repos/objectstack-ai/objectui/issues/12431/assignees (sunhaolin, read back); (2) this os-dev-report via scripts/pm/post-stamped.mjs = POST /repos/objectstack-ai/objectui/issues/12195/comments. No label writes (the PR's 'package: react' and 'tests' labels are labeler.yml's, not mine). git push x2 of the branch (not REST). REST reads: GET objectui issues/12195, issues/12195/comments, pulls/12431, commits/e72618f400/check-runs (x2); GET objectstack issues/comments/6134056653 and 6143275292.", "open_questions": [], "out_of_scope_findings": [ "carrier: domain:ui seat 1 (amend this PR's file surface, or leave it) · noted, not filed. scripts/page-key-read-census.mjs header boundary 4 says 'isDefault is declared and nothing consults it'; this change makes that sentence false. Script prose, not published, no gate reads it; outside the claim's file surface and editing it adds that script's own suite (scripts/__tests__/page-key-read-census.test.ts) as a verification surface, so not touched. Also in the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T05:51Zmain92f979deb, at 2026-10-15T05:51Z. It has one parent,24804dcc6, and is an ancestor oforigin/main.92f979debto the accepted heade72618f400.Fixes #12195.completed.usePageAssignmentopens the object's default record page. It reads the default through the spec'sdefaultRecordPageObject, the same reading every refusing door uses, so there is no tie-break of its own. Ruling A is on spec(page): no stated rule decides between two isDefault record pages for one object, so the renderer cannot honour isDefault without inventing precedence (the spec half of objectui#12195) objectstack#23015, record6134056653.pageNamestill wins.isDefault("default-page selection … objectui page resolution") andNAVIGATION_VIEW_RETIRED's "letisDefaultpick the one that opens" are now true in objectui.6184497895, dev report on this card, ACCEPT6185021939. No contract face applied, so no contract review was owed.Spec Main Shape Gatewas red with objectui#12422's build-order race (6185106215). Its one re-run passed.pm:dispatchedis removed in the same act as this note. The assignee stays as the record of who drove the card.Generated by Claude Code