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
Record page: a related list or rail entry bound to an object that is not installed draws a humanized raw name ("Crm Quote 0 — No related records found", "+3 empty (Crm Quote · …)") #26988
This was measured on 18.2.0 in hotcrm's module-split hard-edge ledger (docs/architecture/module-split-hard-edges.md, rows R18 and M12; hotcrm#2311), with a module's objects absent:
On an opportunity record page (opportunity_detail.page.ts, related lists bound to crm_quote, crm_opportunity_line_item and crm_product), Related shows "Crm Quote 0 — No related records found". The rail reads "+3 empty (Crm Quote · Crm Opportunity Lin…)". The page logs 8 404s on /api/v1/meta/object/… and /api/v1/data/….
On a lead page, Campaign Members shows "Crm Campaign Member 0 — No related records found". The console logs [useSettledSchema] getObjectSchema failed for crm_campaign_member MetadataNotFoundError.
The page still works. The list, though, is noise: it is labelled with a made-up name built from the raw API name, and it offers an empty list of something the install cannot have. Since ADR-0141 shipped, a module artifact can really be absent, so every app record page that names a dependent module's object will show this.
Target
The record page leaves out a related list or rail entry whose object is not installed. It follows the same rule as DashboardView's requiresObject / widget.object filter (ui/packages/app-shell/src/views/DashboardView.tsx:199) and NavigationItem.requiresObject. If it cannot be left out, it carries no humanized raw name and makes no 404 round trips.
Acceptance
With the bound object absent, a record page draws neither the related list nor its rail entry, and makes no /meta/object or /data request for that object. A renderer test pins this.
With the object present, the page is unchanged.
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from the ledger finding F8. A dedupe search for "related list bound to uninstalled object, humanized raw name" found nothing.
Dispatch order · domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · 2026-10-28T08:10Z
You are the os-dev developer agent for exactly ONE card: #26988, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26988" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.
Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktree objectstack-issue-26988 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).
Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26988 --file=F --json run ONCE with output captured to a file (timestamps only as {{WAS:…}} / the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #26988, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran; node scripts/check-sdui-manifest.mjs is a gate on every ui card. Changeset in the root .changeset/ when a published package changes (@object-ui/i18n patch for namespace rows; app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.
Shared files: ui/packages/app-shell/src/views/RecordDetailView.tsx is a lane-wide hot file; seat 5's #26890 (PR #27106, merged 2026-10-28T08:09Z) just added recordPageChildObjects beside the objectui#12244 subscription. Measure open PRs on it at your first edit. ui seat 1 holds nothing on the record page. The dashboard sibling #26971 is ui seat 2's (PR #27065, plugin-dashboard): don't touch dashboard files.
Ruling (cannot be re-ruled)
Triage 2026-10-28T01:02Z: "The fix uses the same rule as DashboardView's requiresObject filter. ⛔ No second existence check." No maintainer ruling.
DashboardView.tsx ≈L199–215 prunes widgets whose requiresObject (default widget.object) is not in the registered object list (metadataObjects names), and defers the prune until that list has loaded (an empty set would hide everything on first render). That is the one existence check to reuse: the registered object list the console already holds. ⛔ No /meta/object/:name probe, no new read.
The record page draws an authored record:related_list block (object in properties.objectName or the node's dataSource.object) and record:reference_rail entries (entries[].objectName) whatever the install holds; with the object absent the block humanizes the raw name ("Crm Quote 0 — No related records found"), the rail reads "+3 empty (Crm Quote · …)", and the page makes /meta/object and /data 404s. Derived related lists (deriveRelatedLists) come from the registered objects, so measure whether they are already safe.
File surface: RecordDetailView.tsx (the prune, beside the effective-page memo), a pure helper beside it if needed, tests; the rail renderer in ui/packages/plugin-detail only if the prune cannot reach rail entries from the page (then a patch changeset). Tests (card acceptance): with the bound object absent, the page draws neither the related list nor its rail entry and makes no /meta/object or /data request for that object; with it present the page is unchanged; before the object list loads nothing is pruned. Ablation: drop the prune and the absence test turns red.
Tier
No path-derived mandate. TIER_DEFAULT, effort medium (S–M). Clause-②: no, Metadata-protocol: none expected. @object-ui/app-shell is private: true: skip-changeset unless plugin-detail changes.
Claim: PM loop round 7
Session: session_01JQwxbXGpVJHZTXXerMoY4C
Account: os-warren
Branch: claude/project-thread-ml8oyu
Worktree: objectstack-issue-26988
Domain: domain:ui
Seat: domain:ui#5
File surface: ui/packages/app-shell/src/views/RecordDetailView.tsx (the prune beside the effective-page memo), a pure helper beside it if needed, tests; ui/packages/plugin-detail rail renderer only if the prune cannot reach rail entries (then a patch changeset) (stop on breach; explain in the report)
Container & model: S–M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: ui/packages/app-shell record page produces it (this lane) | authored related lists and rail entries render whatever the install holds; DashboardView already prunes by the registered object list | every app record page naming a dependent module's object (ADR-0141)
Thread-read: 6463995263
Premise: holds (DashboardView.tsx ≈L199–215 prune; record page draws unregistered objects; on origin/main1900c02c; readings in dispatch order 6463995263)
Serial constraints cleared: ui seat 1 holds nothing in RecordDetailView.tsx or plugin-detail; seat 5's #26890 (same file) merged as PR #27106. The dashboard sibling #26971 is ui seat 2's, other files.
Dispatch order: comment 6463995263. Card thread claude/project-thread-ml8oyu may start editing.
{
"issue": 26988,
"status": "done",
"branch": "claude/project-thread-ml8oyu",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27180",
"head": "7fa6ed4e975e4995814aad67c06c9498e2e78574",
"session": "session_01JQwxbaQJGyCfqGqBduLgbq",
"premise_still_valid": true,
"summary": "RecordDetailView prunes the authored page and a slotted page's slots where assignedPage/assignedSlots resolve: a record:related_list naming an unregistered object (properties.objectName or dataSource.object) is removed, a record:reference_rail entry naming one is removed, and a rail left empty goes with it. Same rule as DashboardView's requiresObject/widget.object filter, same empty-list deferral, identity-preserving. The helper lives beside recordPageChildObjects and shares its two arms. The #26890 child-write subscription reads the pruned page.",
"files_changed": [
"ui/packages/app-shell/src/views/RecordDetailView.tsx",
"ui/packages/app-shell/src/views/recordPageChildObjects.ts",
"ui/packages/app-shell/src/views/recordPageChildObjects.test.ts",
"ui/packages/app-shell/src/views/RecordDetailView.unregisteredObjectLists-26988.test.tsx"
],
"tests": "On 9774d363: RecordDetailView.unregisteredObjectLists-26988 2/2 and recordPageChildObjects 6/6 (lock VERDICT command-exit 0); the whole RecordDetailView* + recordPageChildObjects + appObjectScope set was 77 files / 511 tests passed (VERDICT command-exit 0); pnpm --filter @object-ui/app-shell type-check VERDICT command-exit 0; eslint on the 4 changed files 0 errors. After the origin/main merge, on head 7fa6ed4e: the two new files plus authoredChildWriteRefreshesParent-26890 were 3 files / 12 tests passed. Ablation via scripts/ablation-replace.mjs (anchor 'if (registered.size === 0) return page;' x1 -> x0, replaced with an unconditional return): absent arm red (expected text not to contain 'Crm Quote'), present arm green; restore verified as blob == HEAD 1cf5bc9f and git diff HEAD empty. App-shell tests resolve @object-ui src through vitest aliases, so the ablation needs no dist leg.",
"gates": "dispatch-gates --commands: 24 families (same list before and after the main merge). Pre-merge head: 24 ran; --ran reconciliation 24 derived / 24 run / 0 NOT-MEASURED. 'pnpm --dir ui exec vitest run scripts/' exit 1: one test, scripts/__tests__/check-vi-mock-inherit.test.ts 'no @object-ui/i18n factory freezes the surface', hit its 15000ms timeout while running unlocked beside the locked suite and type-check (174 files passed). Rerun alone under the lock: 83/83 passed, VERDICT command-exit 0. Merged head 7fa6ed4e: the other 23 families rerun, all exit 0: node scripts/check-closing-keyword-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 | node scripts/check-comment-mask-corpus.mjs :: exit 0 | node scripts/check-sdui-manifest.mjs :: exit 0 | node scripts/check-sdui-manifest.mjs --self-test :: exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:dispatcher-error-vocabulary :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:engine-double-contract :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:published-files :: exit 0 | pnpm check:query-options-erasure :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:skill-identifier-liveness :: exit 0 | pnpm check:test-source-alias :: exit 0 | pnpm check:tier-file-adoption :: exit 0 | pnpm check:type-check-coverage :: exit 0 | pnpm check:type-check-debt :: exit 0 | pnpm check:undeclared-dep-imports :: exit 0 | pnpm check:watch-hint-literal :: exit 0 | pnpm check:where-matcher :: exit 0. node scripts/check-sdui-manifest.mjs exit 0 on both heads. pnpm --dir ui run check:new-line-citations: 0 new, exit 0. PR check-runs: in_progress when this report was written.",
"line_budget": "n/a - no ratcheted file touched; 525 changed lines (+516/-9), under the 3000 threshold",
"deviations": [
"The PM assumption named useMetadata().objects (DashboardView's source) as the list to reuse. RecordDetailView reads its own objects prop instead: the useMetadata().objects getter calls ensureType('object'), which loads the whole registry, and on a scoped record route (objectui#13046) that would be a new read the scope exists to avoid. The rule is the same; the list is the one this view already holds, and the same list deriveRelatedLists reads.",
"The PR body folds .github/PULL_REQUEST_TEMPLATE.md headings into the os-dev order (the os-dev exception); lines 1-2 are the platform attribution block and line 3 is Fixes #26988.",
"dogfood_battery: n/a - no ! in the title and no (narrowing) arm."
],
"dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 GitHub MCP calls (subscribe_pr_activity is claude-code-remote, not GitHub MCP)",
"api_writes": "2 relay writes + 1 git push: fleet-write pr_create (PR #27180, 5562 bytes sent = stored, assignee os-warren, label skip-changeset); this os-dev-report comment via post-stamped",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none - noted in the PR's Acceptance notes, not filed: on a scoped record route the scope's own by-name read still asks /meta/object/NAME once for each absent name the page binds (it is the one existence check); a tab or container whose only content was a pruned list stays, empty; record:line_items childObject is not pruned (outside this card's arms)."
],
"body_amendments": []
}
Activity
objectstack-fleet commentedon Oct 28, 2026
Triage: confirmed p3 ·
domain:ui·area:records· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-28T01:02Z⛔ Not a claim.
DashboardView'srequiresObjectfilter. ⛔ No second existence check. Sibling of Dashboard widgets have no audience gate: a tile whose dataset the viewer cannot read draws a permission error, and no widget key can hide it from that viewer #26971 (the dashboard tile) and of [decision] ADR-0141 carve-out: five measured gaps keep an app artifact from compiling or running with a dependent module artifact absent (lookups, actions, widgets, guard hooks, record-change flows) #26986's ADR-0141 module-absence family.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#5·session_01JQwxbXGpVJHZTXXerMoY4C· 2026-10-28T08:10ZYou are the os-dev developer agent for exactly ONE card: #26988, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26988" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktreeobjectstack-issue-26988on that branch from a freshorigin/main;pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=26988 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{WAS:…}}/ the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #26988, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran;node scripts/check-sdui-manifest.mjsis a gate on every ui card. Changeset in the root.changeset/when a published package changes (@object-ui/i18npatch for namespace rows; app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Shared files:
ui/packages/app-shell/src/views/RecordDetailView.tsxis a lane-wide hot file; seat 5's #26890 (PR #27106, merged 2026-10-28T08:09Z) just addedrecordPageChildObjectsbeside the objectui#12244 subscription. Measure open PRs on it at your first edit. ui seat 1 holds nothing on the record page. The dashboard sibling #26971 is ui seat 2's (PR #27065,plugin-dashboard): don't touch dashboard files.Ruling (cannot be re-ruled)
Triage 2026-10-28T01:02Z: "The fix uses the same rule as
DashboardView'srequiresObjectfilter. ⛔ No second existence check." No maintainer ruling.PM mechanism assumptions (measure; disproof welcome)
On
origin/main1900c02c:DashboardView.tsx≈L199–215 prunes widgets whoserequiresObject(defaultwidget.object) is not in the registered object list (metadataObjectsnames), and defers the prune until that list has loaded (an empty set would hide everything on first render). That is the one existence check to reuse: the registered object list the console already holds. ⛔ No/meta/object/:nameprobe, no new read.record:related_listblock (object inproperties.objectNameor the node'sdataSource.object) andrecord:reference_railentries (entries[].objectName) whatever the install holds; with the object absent the block humanizes the raw name ("Crm Quote 0 — No related records found"), the rail reads "+3 empty (Crm Quote · …)", and the page makes/meta/objectand/data404s. Derived related lists (deriveRelatedLists) come from the registered objects, so measure whether they are already safe.RecordDetailViewbefore it renders (blocks and rail entries naming an unregistered object), the wayDashboardViewprunes widgets, with the same "wait until the list has loaded" guard.recordPageChildObjects.ts's walk is the model for finding them; keep Record page: after creating a child from a related list, the parent's header keeps the pre-create rollup (Total Price 32,400 while the server holds 37,800) until a reload #26890's subscription reading the pruned page or the full one, and say which.Scope
File surface:
RecordDetailView.tsx(the prune, beside the effective-page memo), a pure helper beside it if needed, tests; the rail renderer inui/packages/plugin-detailonly if the prune cannot reach rail entries from the page (then a patch changeset). Tests (card acceptance): with the bound object absent, the page draws neither the related list nor its rail entry and makes no/meta/objector/datarequest for that object; with it present the page is unchanged; before the object list loads nothing is pruned. Ablation: drop the prune and the absence test turns red.Tier
No path-derived mandate.
TIER_DEFAULT, effortmedium(S–M).Clause-②: no,Metadata-protocol: noneexpected.@object-ui/app-shellisprivate: true:skip-changesetunlessplugin-detailchanges.objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 7
Session:
session_01JQwxbXGpVJHZTXXerMoY4CAccount:
os-warrenBranch:
claude/project-thread-ml8oyuWorktree:
objectstack-issue-26988Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/app-shell/src/views/RecordDetailView.tsx(the prune beside the effective-page memo), a pure helper beside it if needed, tests;ui/packages/plugin-detailrail renderer only if the prune cannot reach rail entries (then a patch changeset) (stop on breach; explain in the report)Container & model:
S–M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
ui/packages/app-shellrecord page produces it (this lane) | authored related lists and rail entries render whatever the install holds; DashboardView already prunes by the registered object list | every app record page naming a dependent module's object (ADR-0141)Thread-read: 6463995263
Premise: holds (
DashboardView.tsx≈L199–215 prune; record page draws unregistered objects; onorigin/main1900c02c; readings in dispatch order 6463995263)Serial constraints cleared: ui seat 1 holds nothing in
RecordDetailView.tsxorplugin-detail; seat 5's #26890 (same file) merged as PR #27106. The dashboard sibling #26971 is ui seat 2's, other files.Dispatch order: comment 6463995263. Card thread
claude/project-thread-ml8oyumay start editing.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26988, "status": "done", "branch": "claude/project-thread-ml8oyu", "pr": "https://github.com/objectstack-ai/objectstack/pull/27180", "head": "7fa6ed4e975e4995814aad67c06c9498e2e78574", "session": "session_01JQwxbaQJGyCfqGqBduLgbq", "premise_still_valid": true, "summary": "RecordDetailView prunes the authored page and a slotted page's slots where assignedPage/assignedSlots resolve: a record:related_list naming an unregistered object (properties.objectName or dataSource.object) is removed, a record:reference_rail entry naming one is removed, and a rail left empty goes with it. Same rule as DashboardView's requiresObject/widget.object filter, same empty-list deferral, identity-preserving. The helper lives beside recordPageChildObjects and shares its two arms. The #26890 child-write subscription reads the pruned page.", "files_changed": [ "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/app-shell/src/views/recordPageChildObjects.ts", "ui/packages/app-shell/src/views/recordPageChildObjects.test.ts", "ui/packages/app-shell/src/views/RecordDetailView.unregisteredObjectLists-26988.test.tsx" ], "tests": "On 9774d363: RecordDetailView.unregisteredObjectLists-26988 2/2 and recordPageChildObjects 6/6 (lock VERDICT command-exit 0); the whole RecordDetailView* + recordPageChildObjects + appObjectScope set was 77 files / 511 tests passed (VERDICT command-exit 0); pnpm --filter @object-ui/app-shell type-check VERDICT command-exit 0; eslint on the 4 changed files 0 errors. After the origin/main merge, on head 7fa6ed4e: the two new files plus authoredChildWriteRefreshesParent-26890 were 3 files / 12 tests passed. Ablation via scripts/ablation-replace.mjs (anchor 'if (registered.size === 0) return page;' x1 -> x0, replaced with an unconditional return): absent arm red (expected text not to contain 'Crm Quote'), present arm green; restore verified as blob == HEAD 1cf5bc9f and git diff HEAD empty. App-shell tests resolve @object-ui src through vitest aliases, so the ablation needs no dist leg.", "gates": "dispatch-gates --commands: 24 families (same list before and after the main merge). Pre-merge head: 24 ran; --ran reconciliation 24 derived / 24 run / 0 NOT-MEASURED. 'pnpm --dir ui exec vitest run scripts/' exit 1: one test, scripts/__tests__/check-vi-mock-inherit.test.ts 'no @object-ui/i18n factory freezes the surface', hit its 15000ms timeout while running unlocked beside the locked suite and type-check (174 files passed). Rerun alone under the lock: 83/83 passed, VERDICT command-exit 0. Merged head 7fa6ed4e: the other 23 families rerun, all exit 0: node scripts/check-closing-keyword-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 | node scripts/check-comment-mask-corpus.mjs :: exit 0 | node scripts/check-sdui-manifest.mjs :: exit 0 | node scripts/check-sdui-manifest.mjs --self-test :: exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:dispatcher-error-vocabulary :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:engine-double-contract :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:published-files :: exit 0 | pnpm check:query-options-erasure :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:skill-identifier-liveness :: exit 0 | pnpm check:test-source-alias :: exit 0 | pnpm check:tier-file-adoption :: exit 0 | pnpm check:type-check-coverage :: exit 0 | pnpm check:type-check-debt :: exit 0 | pnpm check:undeclared-dep-imports :: exit 0 | pnpm check:watch-hint-literal :: exit 0 | pnpm check:where-matcher :: exit 0. node scripts/check-sdui-manifest.mjs exit 0 on both heads. pnpm --dir ui run check:new-line-citations: 0 new, exit 0. PR check-runs: in_progress when this report was written.", "line_budget": "n/a - no ratcheted file touched; 525 changed lines (+516/-9), under the 3000 threshold", "deviations": [ "The PM assumption named useMetadata().objects (DashboardView's source) as the list to reuse. RecordDetailView reads its own objects prop instead: the useMetadata().objects getter calls ensureType('object'), which loads the whole registry, and on a scoped record route (objectui#13046) that would be a new read the scope exists to avoid. The rule is the same; the list is the one this view already holds, and the same list deriveRelatedLists reads.", "The PR body folds .github/PULL_REQUEST_TEMPLATE.md headings into the os-dev order (the os-dev exception); lines 1-2 are the platform attribution block and line 3 is Fixes #26988.", "dogfood_battery: n/a - no ! in the title and no (narrowing) arm." ], "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 GitHub MCP calls (subscribe_pr_activity is claude-code-remote, not GitHub MCP)", "api_writes": "2 relay writes + 1 git push: fleet-write pr_create (PR #27180, 5562 bytes sent = stored, assignee os-warren, label skip-changeset); this os-dev-report comment via post-stamped", "open_questions": [], "out_of_scope_findings": [ "carrier: none - noted in the PR's Acceptance notes, not filed: on a scoped record route the scope's own by-name read still asks /meta/object/NAME once for each absent name the page binds (it is the one existence check); a tab or container whose only content was a pruned list stays, empty; record:line_items childObject is not pruned (outside this card's arms)." ], "body_amendments": [] }