Skip to content

feat(spec): a report nav item without reportName opens the app's report index; the console stops injecting All reports - #27566

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-1iqx7w
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-1iqx7w

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP

Requested by Yibo Wang · project thread

Fixes #27525

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none edited. Four governed hits teach a report nav item and all stay true for a named report; none teaches the index form, and none teaches that reportName is required: skills/objectstack-ui/SKILL.md:171 (the report row lists reportName, label, icon, "Link to a report"), skills/objectstack-ui/rules/navigation.md:38 (a named example), skills/objectui/guides/app-composition.md:21 ({type:'report', reportName} routes to /report/:name) and :34 (emit the typed target field for the chosen type). Named, not edited.

ADR-0087: a widening, not a retirement. Nothing an author wrote before parses differently.

The claim's Metadata-protocol: none is measured as added. The order says "as measured", and AGENTS.md's kinds put "a new accepted value" under added: { type: 'report' } with no reportName was refused and is now accepted. The changesets carry added, and every package the PR moves is graded minor: @objectstack/spec, @objectstack/cli (help text only, but the PR-wide added sets its floor), @object-ui/layout, @object-ui/app-shell, @object-ui/i18n and @object-ui/types.

Migration (in the @object-ui/app-shell changeset): an app that relied on the injected row adds one entry to its navigation where the index belongs: { id: 'nav_reports', type: 'report' }.

维护者速读

  • 改了什么:导航项 type: 'report' 不写 reportName 时表示「本应用的报表索引」(/apps/APP/reports);控制台不再给每个应用自动加「全部报表」行,只有导航里声明了这一项才画出来;Studio 与元数据编辑器的导航检查器都把未选报表的报表项显示为「报表索引」,选它会删掉 reportName 键而不是存 '';os explain app、两页文档和 QA 清单同步改写。
  • 为什么改:维护者定了「加上才有」:索引入口由作者自己加。原来每个非 Studio 应用都被注入一行,没有报表的应用也有;而 spec 又不允许作者自己声明一个索引项。
  • 风险与回滚:放宽接受集,原来能解析的导航照旧;依赖注入行的应用会少一个入口,迁移是加一行 { type: 'report' },仓内自带报表的 showcase 与 todo 示例已加。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/layout/UnifiedSidebar.tsx(删掉的注入块)、ui/packages/layout/src/NavigationRenderer.tsx(resolveHref 的 report 分支与无标签时的 reportIndex 询问)、ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx(NAV_TARGET_OPTIONAL,保存不再丢掉索引项)。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/layout/__tests__/UnifiedSidebar.reportIndexEntry-27525.test.tsx,应见 6 个用例全绿:未声明时无「All reports」行,声明后链接到 /apps/crm/reports 且在该路由高亮。
  • 需要你做的:无,席位落地。

What changes

Measured on origin/main 57c51aab (before).

  1. Spec: NavigationItemSchema.safeParse({ id: 'nav_reports', type: 'report' }):

    bare report item: [{"expected":"string","code":"invalid_type","path":["reportName"],"message":"Invalid input: expected string, received undefined"}]
    named report item: ACCEPTED
    empty-string reportName: ACCEPTED
    
  2. Console: UnifiedSidebar.tsx drew a static row under {!isStudioApp && (…)} after the app's NavigationRenderer: a Link to ${basePath}/reports with data-testid="sidebar-report-index", text sidebar.allReports ("All reports" / 「全部报表」), for every non-Studio app, whatever its navigation or its reports. Pinned on main by UnifiedSidebar.reportIndexLink-13020.test.tsx ("links the app's report index, though the navigation names no report").

  3. ⌘K: the card's premise does not hold on main. CommandPalette.tsx already lists reports beyond the navigation: useAppReportEntries(activeApp, open && !inStudio, gates) (views/appReportIndex.ts, objectui#13020 / os migrate plan prints 4 ERROR records with stack traces on a not-yet-migrated database — every one of them is a read whose caller treats absence as a normal answer #13273 / objectstack#26892), the reports the app ships (its package's own, plus those its served navigation names), limited by mayOpenReport to the ones whose datasets read objects the member may read, and by the navigation's gates. That is the same list AppReportIndexView shows. Pinned on main by AppReportIndex-13020.test.tsx ("⌘K offers the app's reports beyond its navigation": a report no entry names is offered to a reader; one on an unreadable object, a joined report with such a block, and another package's report are not). The HotCRM comment the card quotes predates it. So Target 3 needed no new reader; this PR reuses that one and pins it under an index-only navigation (below).

After (head dd0bd483; unchanged at 19f4cf83). The same probe answers bare report item: ACCEPTED; the other two lines are unchanged.

The spec (packages/spec/src/ui/app.zod.ts)

ReportNavItemSchema.reportName is z.string().optional(). Its describe: "Target report name. Omit it and the entry opens the app's report index (the reports the app ships that the member may open) instead of one report." The variant's TSDoc states the two targets and that the console draws an index entry only where the navigation declares one. The unknown-key teaching at navItemSurface (reportname → "type: 'report' (with reportName)" on every other variant) stays true and is pinned. stack.zod.ts's cross-check of a named reportName against reports reads typeof nav.reportName === 'string', so an absent name skips it; lint validate-nav-target-refs likewise skips an absent target. Regenerated: content/docs/references/ui/app.mdx (five reportName rows go from ✅ to optional with the new text). check:generated reports the other 13 artifacts current, check:authorable-surface included: the base lists keys (ui/ReportNavItem:reportName stays), not required-ness, so it does not move. No metadata-form row or form translation names reportName, so no bundle moves.

Readers of a nav item's reportName, measured

Reader Absent reportName, before Change
layout resolveHref (sidebar link, mobile tab bar, ⌘K, Studio preview, active-row election) #, a dead entry BASE/reports; the active-row election reads the same answer, so the entry lights on the index route (exact match) and not on report/NAME
layout inheritedNavItemLabel (label-less entry) the entry's id asks the host's resolver for the new { kind: 'reportIndex' } target, else the id
app-shell useNavTargetLabel (the console's resolver) n/a answers sidebar.allReports ("All reports" / 「全部报表」), the injected row's own text
app-shell UnifiedSidebar injected an index row for every non-Studio app injects nothing; the declared entry is drawn by NavigationRenderer
app-shell CommandPalette n/a (an absent name would put undefined in the set of nav-named reports and in the item value) the index entry is offered as itself; only a non-empty reportName counts as nav-named; the value never holds undefined
app-shell appReportIndex (reportNamesIn) already skipped a non-string name unchanged
app-shell AppHeader (breadcrumb) reads the route, not the nav item unchanged
Studio nav-item inspector (StudioDesignSurface) "unbound", with the "left out of the saved navigation" hint not unbound; the report picker's empty face reads 「报表索引」 / "Report index", with a hint saying what it opens; picking it removes reportName
Studio save (AppNavCanvas navPayloadOf) left the entry out of the save kept (NAV_TARGET_OPTIONAL); the file's own parity pin (left out exactly when the spec refuses it bare) now judges report taken bare
metadata-admin AppNavInspector the combo field, empty; it offered reports only, so once a report was picked there was no way back to the index the report combo leads with a "Report index" row (new optional emptyLabel on InspectorComboField), and picking it REMOVES reportName, as the Studio does, never writing ''; the 「报表索引」 hint shows while reportName is absent; its live preview shows /apps/APP/reports
metadata-admin AppPreview dropped a label-less, childless entry with no target keeps it: the target reads reports
navSurface bindTarget (what the Studio rail opens) null unchanged: the index is no metadata item to design

Shipped apps

Every non-Studio app showed the injected row. Read as the maintainer's 「加上才有」: the apps this repo ships that ship reports get the entry, so none loses a working index; an app with no report had an index that said "No reports", and gets nothing.

  • examples/app-showcase (four reports, all named in its navigation): { id: 'nav_report_index', type: 'report', icon: 'bar-chart-3' }, appended last in its reports group so no existing index path moves (nav-and-detail-grants.test.ts pins navigation[3].children[11]).
  • examples/app-todo (ships task.report.ts, which its navigation never named, so the injected row was its only door): { id: 'nav_reports', type: 'report', icon: 'bar-chart-3' } in its Analytics group.
  • examples/app-crm, examples/app-multi-package: ship no report, unchanged. No package under packages/ ships a report.

Both new entries carry no label, so they show the console's own name for the index and need no translation key.

Tests

  • Spec pin (packages/spec/src/ui/app.test.ts, replacing "should reject report nav item without reportName"): a bare report item parses on the variant, through NavigationItemSchema and inside an AppSchema, and holds no reportName after parse; an unknown key on it is still refused, and reportName on a page item still answers "type: 'report' (with reportName)"; the describe names the report index.
  • Console (UnifiedSidebar.reportIndexEntry-27525.test.tsx, the 13020 sidebar file moved and rewritten, since its first assertion is now the opposite behaviour): (a) no injected row on an app whose navigation declares no index entry (no "All reports" link, no /apps/crm/reports href, no sidebar-report-index); (b) a declared label-less index entry links /apps/crm/reports named "All reports", a labelled one shows its label; (c) it is the active row on /apps/crm/reports and the named report entry is not; (d) drawing it reads no report list; CONTROL: a named report entry still routes to /apps/crm/report/crm_pipeline.
  • ⌘K (AppReportIndex-13020.test.tsx, new block): under a navigation that declares only { type: 'report' } and names no report, the palette finds "Win Rate" and "Pipeline", and not "Case Aging" (unreadable object), "Mixed Blocks" (a block on it) or "Headcount" (another package); it offers the index entry itself, and its value holds no undefined.
  • Studio: StudioDesignSurface.navItemTypes-11790 (a report entry is born as the index, parses, is sent, shows the index hint and no "left out" hint, the picker offers Report index first; a pick writes reportName; the index face removes it again), StudioDesignSurface.sharedSelect-11865 (reportBorn and a new reportBound row on the index face), AppNavCanvas.navPayload-11776 (bare report taken and sent).
  • metadata-admin (AppNavInspector.reportIndexClear-27525.test.tsx, new): picking "Report index" on a named report entry writes it with no reportName key; the trigger names the index while the key is absent; CONTROL: picking a report writes its name; CONTROL: a page entry's combo offers no empty row. Ablated (the empty pick routed back to patch({ reportName: '' })): the first case goes red, the other three stay green; restored.
  • Updated for the removed row: UnifiedSidebar.iconModeLabelFocus-12795 (the tab walk no longer ends on "All reports") and UnifiedSidebar.favoritesMenu-12427 (no row to exclude).
Runs

All on the shared container, under scripts/pm/os-verify-lock.sh.

  • Spec, at 0b6a152e (pre-merge): pnpm --filter @objectstack/spec test → Test Files 751 passed (751), Tests 22330 passed | 1 todo; test:repo → Test Files 86 passed (86), Tests 1432 passed; typecheck → VERDICT command-exit 0 (check:test-typecheck: OK).
  • Spec, after merging origin/main (head dd0bd483): build + check:generated → "All 14 generated artifacts are up to date"; test → Test Files 752 passed (752), Tests 22341 passed | 1 todo.
  • ui, 138 files that import or pin the edited modules (the sidebar, ⌘K, report index, Studio nav inspector and save, metadata-admin nav inspector / canvas / preview, @object-ui/layout, the @object-ui/types nav and mirror-parity suites), cd ui && pnpm exec vitest run --maxWorkers=3 FILES: first run 4 red, all four pinning the old behaviour (a bare report entry unbound / left out of the save / not taken bare); updated as listed above. Final, on dd0bd483: Test Files 138 passed (138), Tests 1755 passed (1755). Narrowed to these files on purpose; CI's Test UI runs the rest.
  • ui type-check, @object-ui/types, @object-ui/layout, @object-ui/i18n, @object-ui/app-shell: all four type-check: Done, VERDICT command-exit 0. The full turbo run build --concurrency=2 (115/115 tasks, the console's tsc included) is green too.
Ablation

One ablation, of the behaviour the console pins test: resolveHref's index answer put back to '#' with scripts/ablation-replace.mjs (ui/packages/layout/src/NavigationRenderer.tsx, anchor hit 1 → 0, blob 9f866973 → f4b35fdd), then the sidebar and report-index files run (the ui suites alias @object-ui/layout to its source, so no build sits between): Tests 3 failed | 13 passed: (b) both "draws a declared index entry" cases and (c) "the active row" went red. Restored: blob 9f866973 == HEAD, git diff HEAD empty. The ⌘K index-entry pin stayed green under it, as expected: it asserts the entry is offered, not where it goes. (a) "no injected row" is the inverse of the assertion main pinned (UnifiedSidebar.reportIndexLink-13020.test.tsx: "links the app's report index, though the navigation names no report"), so it is red against main's sidebar by construction.

Acceptance notes

  • reportName: '' was accepted by the spec before and still is (this PR widens absence only). The console read '' as falsy and answered #; it now answers the index, the same as absence.
  • The mobile tab bar (AppSchemaRenderer) takes no label resolver, so a label-less index entry there shows its id, as every other label-less entry already does in that host.
  • reportName: '' stays accepted by the spec and refused by defineStack when the app declares reports; this PR does not rule on it. Both inspectors now reach the index by removing the key, so neither writes ''.
  • Teaching text (added to the file surface by the claim amendment, comment 6497681948): os explain app (packages/cli/src/commands/explain.ts) now says a report item may omit reportName to open the index; content/docs/ui/apps.mdx (Report Navigation, and the label-fallback paragraph) and content/docs/ui/reports.mdx document { type: 'report' } and say the console adds no index entry of its own.
  • QA ledger: docs/qa/platform-checklist/areas/platform-core.json still told runners the console adds the "All reports" row to every app. platform-core.nav-surfaces-render (r8: steps[2], acceptance[1] clause and verify) and platform-core.builtin-apps-nav-render (r10: steps[9]) now say the index is a declared, served entry, each with a history row; check:platform-checklist OK. History rows that record the old row are left as written.
  • The skills' index form (skills/objectstack-ui/SKILL.md:171, rules/navigation.md:38, skills/objectui/guides/app-composition.md:21,34) is carded separately by seat 3.
  • The Studio's "+" still sends a new report entry into the create-a-report flow; an index entry is made by choosing the report type on an entry and leaving the report unpicked.

Verification

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 108 commands at 01ed76af and the same 108 at 0b6a152e. Run serially; first pass 103 exit 0, pnpm --dir ui exec vitest run scripts/ exit 1 (two 15 s sweep timeouts beside a locked run: check-vi-mock-inherit.test.ts, js-comment-mask-returns-interpolation-9424.test.ts; rerun alone: Test Files 2 passed (2), Tests 91 passed; the rest of that run: Tests 4899 passed), and three exit 3 (prerequisite: no dist) that went to exit 0 after the full build: check:skill-examples ("274 prose examples type-check"), check:dual-build-cjs-loads ("112 published require entry point(s) across 69 package(s) load"), check:i18n-coverage ("OK (14 config(s), 527 baselined untranslated string(s), none new)").
  • dispatch-gates --ran: "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN".
  • After the changeset grades moved to minor and the merge: every changeset gate in the list again (check-changeset-no-major --base origin/main, check-empty-changeset and its self-test, check:changeset-gate-self-tests, check:pm-changeset-deadline-census): exit 0. GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event (this body): exit 0, "LEVEL AXIS: this PR declares clause-② yes (widening) and metadata-protocol added, and no package … it moves is graded patch".
  • gen:authorable-surface-base refuses on this shallow clone (it cannot establish the anchor's ancestry); check:authorable-surface is green inside check:generated, and the base, a key list, has no entry this change moves.
  • Round 2 (head 19f4cf83; origin/main 72b08eec merged at fad30718). The teaching-text paths derive 85 commands, run serially: 83 exit 0, and check:docs (exit 1) and check:skill-examples (exit 3) refused because the spec build predated the merge; both exit 0 after pnpm --filter @objectstack/spec build. The inspector paths derive 34, all exit 0. Over the whole branch the list is now 111: the 108 above plus check:i18n, check:i18n-walk-parity and check:platform-checklist, all green. dispatch-gates --ran: "111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN". cd packages/cli && pnpm test: Test Files 529 passed | 1 skipped (530), Tests 6602 passed | 9 skipped. ui metadata-admin inspectors: Test Files 205 passed (205), Tests 2786 passed | 1 skipped. @object-ui/app-shell type-check: exit 0.
  • Not run locally: the dogfood battery (no ! in the title and no (narrowing) arm); the repo-wide CI families. CI's check-runs are the record for those.

Generated by Claude Code

…declares it

The sidebar stops injecting its All reports row; a report nav entry with no
reportName is the app's report index, routed to /reports and named by the
console. The Studio shows that entry as the report index and keeps it on save.
The showcase and todo examples, which ship reports, declare the entry.

Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec, touching 2 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/field-level-security.mdx (via os explain (command, read off packages/cli/src/commands/explain.ts))
  • content/docs/protocol/objectql/security.mdx (via os explain (command, read off packages/cli/src/commands/explain.ts))
  • content/docs/ui/react-pages.mdx (via os explain (command, read off packages/cli/src/commands/explain.ts))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via os explain (command, read off packages/cli/src/commands/explain.ts))
  • content/docs/releases/v18/18-2.mdx (via os explain (command, read off packages/cli/src/commands/explain.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 176 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 178b5238630be52ad6e122b7336d6cac81cb1960 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 341914f47060499858032506b83e6cdaadea14d8 — the merge of head 19f4cf83abf7a7e827614166658cabbc9f9bceab into base 178b5238630be52ad6e122b7336d6cac81cb1960, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 341914f47060499858032506b83e6cdaadea14d8 && git checkout 341914f47060499858032506b83e6cdaadea14d8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 178b5238630be52ad6e122b7336d6cac81cb1960 19f4cf83abf7a7e827614166658cabbc9f9bceab && git checkout -B drift-repro 178b5238630be52ad6e122b7336d6cac81cb1960 && git merge --no-ff 19f4cf83abf7a7e827614166658cabbc9f9bceab

node scripts/docs-audit/affected-docs.mjs --json 178b5238630be52ad6e122b7336d6cac81cb1960

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 178b5238630be52ad6e122b7336d6cac81cb1960 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ted "All reports" row from docs and the QA checklist (#27525)

- `os explain app` names the bare `report` item as the report index target.
- content/docs/ui/apps.mdx and reports.mdx document `{ type: 'report' }` with no
  `reportName`, and say the console draws no index entry of its own.
- platform-core.nav-surfaces-render (r8) and builtin-apps-nav-render (r10): the
  console-added destination the walks named is gone; the index is now a served
  nav entry.
- @objectstack/cli patch changeset.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

Contract-review-claim: session_01AavokzJ5DndAwitDXvKy4U · head dd0bd483ff19e86361f5546ea86318bc76731327 · 2026-10-29T14:01Z

Triage seat, taking needs:contract-review on this head (#27062 (a)). The review runs at CONTRACT_REVIEW_TIER. If the lane claims or posts a record on this head first, triage withdraws.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dd0bd483ff19e86361f5546ea86318bc76731327
Local-runs: none
Scope: packages/spec/src/ui/app.zod.ts + packages/spec/src/stack.zod.ts, packages/lint/src/validate-nav-target-refs.ts, packages/spec/authorable-surface.base.json, content/docs/references/ui/app.mdx, ui/packages/layout/src/NavigationRenderer.tsx, ui/packages/app-shell/src/console/AppContent.tsx, ui/packages/app-shell/src/console/appRouteMetadataReads.ts, ui/packages/app-shell/src/hooks/useNavTargetLabel.ts, ui/packages/app-shell/src/chrome/CommandPalette.tsx, ui/packages/app-shell/src/views/appReportIndex.ts, ui/packages/app-shell/src/views/metadata-admin/inspectors/AppNavInspector.tsx, ui/packages/app-shell/src/views/metadata-admin/inspectors/nav-target.ts, ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx, ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx, ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, ui/packages/app-shell/src/views/studio-design/navSurface.ts, packages/cli/src/commands/explain.ts, skills/objectstack-ui/SKILL.md, skills/objectstack-ui/rules/navigation.md, skills/objectui/guides/app-composition.md

Inputs read: card #27525 body and all three comments (claim, os-dev-report, claim amendment); PR #27566 body, file list (29 paths) and the net diff vs main at the head; the 70 check-runs on dd0bd483 — every one completed, each success or skipped (opt-in / matrix-conditional rows), none pending, none failed. Ruling-ref on the card: none (the maintainer's target is quoted verbatim in the card body), so no further gh api read was owed. The repo at /home/user/objectstack was read with git show prr-27566:PATH only.

① Derived judgments

  1. Accept set, ReportNavItemSchema — reportName: z.string() becomes z.string().optional() inside the unchanged strictObject(navItemSurface('report'), …). Every shape that parsed on main parses identically; { type: 'report' } with no reportName is newly accepted; unknown keys are still refused (the variant stays strict; app.test.ts pins report: 'pipeline' refused). A pure widening. Right.
  2. Cross-variant teaching — navItemSurface still maps reportname to type: 'report' (with reportName) on every variant but report (variant !== 'report' guard untouched); pinned in the new spec test. The prose is a prescription for a misplaced key and stays correct for the author who wrote it. Right.
  3. Stack cross-check — stack.zod.ts:4595 fires only when typeof nav.reportName === 'string', so an absent name is skipped; a named one is still checked against reports. Right.
  4. Lint validate-nav-target-refs — strName answers undefined for a missing or empty target and the loop continues, so a bare index entry raises no nav-target-unresolved. Right.
  5. Public TS surface, @objectstack/spec — ReportNavItem['reportName'] goes from string to string | undefined. In-repo readers: resolveHref / inheritedNavItemLabel already branched on falsy; CommandPalette's as string cast is guarded; appReportIndex.reportNamesIn already required a non-empty string; navSurface.bindTarget answers null for a bare entry (the index is no metadata item to design). Type Check UI / workspace / consumer gates green. Right.
  6. @object-ui/layout public surface — NavLabelTarget gains { kind: 'reportIndex' }; resolveHref answers BASE/reports instead of # for a bare report entry. The route exists (AppContent.tsx:177 / :1478, AppReportIndexView; appRouteMetadataReads.ts:65 lists reports). The active-row scorer gives MATCH_EXACT on /apps/APP/reports, and /apps/APP/report/NAME does not start with /apps/APP/reports/, so neither entry lights for the other. A host resolver without the new case falls to default: undefined and then to item.id. Right; a consumer-visible widening, carried as minor.
  7. @object-ui/app-shell — the injected sidebar-report-index row is removed for every non-Studio app; the index is drawn only where declared; useNavTargetLabel answers sidebar.allReports for reportIndex; the Studio inspector no longer marks a bare report entry unbound and navPayloadOf keeps it (NAV_TARGET_OPTIONAL); metadata-admin's AppNavInspector shows the index hint and AppPreview.navTarget answers reports. Behaviour change for apps that relied on the row; the migration line is in the app-shell changeset. Right against the maintainer's 「加上才有」.
  8. ⌘K (card Target 3) — the premise did not hold on main: CommandPalette already reads useAppReportEntries(activeApp, open && !inStudio, gates) (objectui#13020 lineage) — verified in the live tree at CommandPalette.tsx:257. The PR reuses it and pins it under an index-only navigation; navReportNames now excludes absent and empty names so the index entry leaves every report to the registry list. Right; no new reader was owed.
  9. @object-ui/types mirror — describe text only on the already-optional reportName; no shape change. Right.
  10. @object-ui/i18n — two new studio keys, en and zh (reportIndex = 「报表索引」, the face the card's Acceptance names). Right.
  11. Generated reference — content/docs/references/ui/app.mdx: five reportName rows go from ✅ to optional with the describe text (the PR body says four; the tree has five, and check:generated is green inside Lint & Repo Gates — the tree is judged, the count is a prose slip). Right.
  12. Authorable-surface base / ADR-0087 — the base is a key list; ui/ReportNavItem:reportName is present in both authorable-surface.base.json:7891 and authorable-surface/ui.json:1277; a widening needs no tombstone, conversion or chain step. Spec property liveness green. Right.
  13. Shipped apps — examples/app-showcase (reports in src/ui/reports/) and examples/app-todo (task.report.ts) gain a bare index entry; examples/app-crm and examples/app-multi-package define no report (grep for Report.create / type: 'report' empty). packages/qa/downstream-contract ships pipeline.report.ts but declares no app, so the PR body's "no package under packages/ ships a report" is loose but nothing there loses a door. Right in effect.

② Semver level

Five root changesets, all minor: @objectstack/spec, @object-ui/layout, @object-ui/app-shell, @object-ui/i18n, @object-ui/types. Each carries Clause-②: yes (widening) and Metadata-protocol: added. Per AGENTS.md's kinds, a newly accepted value is added, and a kind takes at least minor on a package the diff moves — so minor is the floor on all five, the types describe-only package included (the declaration is PR-wide and the diff moves it). examples/* are not published packages and need none. Both Check Changeset runs are green. The diff publishes exactly a widening of the metadata accept set plus consumer behaviour that follows it; nothing is removed or refused, so no (narrowing) arm and no BREAKING banner is owed. The app-shell changeset carries the one-line migration the card's Acceptance asks for.

Clause-②: yes (widening) — matches the diff (the report variant's accept set widens; nothing an author wrote parses differently). The claim's original Metadata-protocol: none was wrong; the dev measured added and the seat's amendment adopted it. Right.

③ Boundary flags

Dev flags (deviations in the os-dev-report), each answered:

  1. Metadata-protocol: none → added, changesets regraded to minor. Correct per AGENTS.md; the seat's claim amendment (comment 6497681948) adopts it. Answered.
  2. Teaching text (packages/cli/src/commands/explain.ts:226, content/docs/ui/apps.mdx, content/docs/ui/reports.mdx) edited then backed out to stay inside the claim's file surface; a patch is held by seat 3. Escalated: the seat's amendment, posted after the PR opened, widens the file surface to those three files "so they teach the index form in this PR" — but the head dd0bd483 does not contain them. The card record and the PR disagree. explain.ts:226 still says every item needs "that type's own target key … report, reportName", a requirement the spec no longer has. None of the three is a hits file, nothing they say makes a refused shape, and the card's Acceptance ("the describe and the generated reference say what it does") is met in the tree, so this is a should-fix follow-up, not a FAIL: either the seat lands its held patch as the follow-up PR or corrects the amendment to say the teaching text ships separately.
  3. Worktree kept for a patch round. Process only; no bearing on the tree. Answered.
  4. UnifiedSidebar.reportIndexLink-13020.test.tsx moved and rewritten as …reportIndexEntry-27525.test.tsx: its first assertion pinned the opposite behaviour ("links the app's report index, though the navigation names no report"), so replacement, not duplication, is right. Answered.

open_questions: empty. out_of_scope_findings: the teaching-text patch (above); the Studio "+" still routes a new report entry into the create-a-report flow, and an index entry is made by choosing report and leaving the pick empty — a UX note, not a contract gap; the HotCRM follow-up (card Target 4) is to be filed after this lands. Card Target 2's "Studio unchanged" refers to the Studio app's own sidebar (no injection before or after); the Studio navigation inspector change is the card's own Acceptance item.

Skill teaching (governed, not in this PR's file list): skills/objectstack-ui/SKILL.md:171, rules/navigation.md:38, skills/objectui/guides/app-composition.md:21,34 all describe a named report entry and stay true; none teaches the index form. Same carrier as item 2 — should-fix follow-up.

④ Design judgment

packages/spec/src/ui/app.zod.ts — judged against the maintainer's ruling quoted in the card (「加上才有」; Target 1) and its first consumer, resolveHref in @object-ui/layout, read in the live tree.

Holds:

  • The ruling asks for one variant with two targets chosen by presence of reportName. .optional() is the minimal spelling of that; the describe states both targets and what the index lists ("the reports the app ships that the member may open"), which is exactly useAppReportEntries' set (mayOpenReport over readable datasets, plus nav gates). The TSDoc also states the console's non-injection, so the schema file carries the whole contract an author needs.
  • type stays the discriminant of the discriminatedUnion; an optional sibling field does not disturb it.
  • Strictness: the variant is still strictObject(navItemSurface('report')); the PR's own pin refuses report: 'pipeline' on a bare entry and keeps the reportname prescription on other variants. The alias-integrity PROSE_ALIAS_TARGETS allowlist is untouched because the prose target string is unchanged.
  • First consumer: resolveHref bare → ${basePath}/reports, a served route; the active-row scorer elects it exactly on the index route and never on report/NAME. The label ladder asks { kind: 'reportIndex' }, which the console answers with the text the injected row showed. The three save paths (Studio navPayloadOf, metadata-admin AppPreview/AppNavInspector, stack.zod.ts) and the lint all agree with the spec on absence.
  • A NavigationContribution injecting { type: 'report' } into another app resolves to the host app's index — "the app's report index" in the describe reads correctly for that case too.
  • No consumer filter hides a target-less entry: passesNavItemGuards reads only visible, requiredPermissions, requiresObject/requiresService and the doc check.
  • ADR-0087: widening; no retirement surface touched; the authorable-surface base (a key list) does not move.

Tried to break it:

  • reportName: '' — the spec accepted the empty string before and still does (this PR widens absence only). The console (resolveHref, AppPreview, AppNavInspector's hint, CommandPalette's exclusion) now reads '' as the index, the same as absence, while stack.zod.ts:4595 reads '' as a name and refuses it when the app declares any report ("references report '' which is not defined"), and the lint skips it. metadata-admin's inspector combo writes '' on clear (patch({ [targetKey]: v })), whereas the Studio inspector removes the key — two inspectors produce two shapes for the same face. Nothing that worked on main breaks, '' was never a working shape, and the card did not rule on it, so this is a should-fix, not a must-fix: either the describe says '' is read as absence, or .min(1) is ruled (a narrowing, out of this card), or the metadata-admin combo drops the key on clear as the Studio does.
  • Mobile tab bar (AppSchemaRenderer.tsx:371) calls resolveNavItemLabel(item) with no resolver, so a label-less index entry shows nav_reports there. Pre-existing host behaviour for every label-less entry; the shipped examples carry no label by design. Cosmetic; should-fix at most, outside the spec file.
  • Cross-variant teaching wording — type: 'report' (with reportName) is now one of two legal forms; still the right prescription for a misplaced reportname key. No finding.
  • Teaching text outside the hits (explain.ts:226, skills, docs) now understates the accept set (see ③). Not a hits-file defect.

Must-fix: none. Should-fix: the '' ambiguity (describe or metadata-admin clear path); the teaching-text follow-up the seat holds; the skills' index form.

Implemented-by: claude/project-thread-1iqx7w
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

…ex by removing reportName (#27525)

The navigation inspector's report combo offered reports only, so once a
report was picked there was no way back to the index, and the only empty
value the console reads as the index, `''`, is one defineStack refuses as
an unknown report. The combo now leads with a "Report index" row (a new
optional `emptyLabel` on InspectorComboField), and picking it removes the
key, as the Studio inspector does. Pinned in
AppNavInspector.reportIndexClear-27525.test.tsx.

The @objectstack/cli changeset is graded minor with the PR-wide
`Metadata-protocol: added` declaration.

Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l and removed size/m labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP

维护者速读 (席位终稿)

  • 改了什么:导航项 type: 'report' 不写 reportName 时表示「本应用的报表索引」(/apps/APP/reports)。控制台不再给每个应用自动加「全部报表」行,只有导航里声明了这一项才画出来。Studio 和元数据编辑器的导航检查器都把未选报表的报表项显示为「报表索引」,选它会删掉 reportName 键,而不是存 ''。os explain app、两页文档和 QA 清单同步改写。
  • 为什么改:维护者定了「加上才有」,索引入口由作者自己加。原来每个非 Studio 应用都被注入一行,没有报表的应用也有;spec 又不允许作者自己声明一个索引项。
  • 风险与回滚:只放宽接受集,原来能解析的导航照旧。依赖注入行的应用会少一个入口,迁移办法是加一行 { type: 'report' };仓内自带报表的 showcase 和 todo 示例已经加上。回滚 = revert 本 PR。
  • 先看:ui/packages/app-shell/src/layout/UnifiedSidebar.tsx(删掉的注入块)、ui/packages/layout/src/NavigationRenderer.tsx(resolveHref 的 report 分支)、ui/packages/app-shell/src/views/metadata-admin/inspectors/AppNavInspector.tsx(清空时删键)。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/layout/__tests__/UnifiedSidebar.reportIndexEntry-27525.test.tsx,6 个用例全绿:未声明时没有「All reports」行;声明后链接到 /apps/crm/reports,并在该路由高亮。
  • 需要你做的:无,席位落地。

席位意见:接受。分诊契约复核 PASS(6498961917,head dd0bd483)。之后的变化不碰 packages/spec/src,是复核的两条 should-fix('' 清空路径、教学文字),由席位在本 head 上核对。

席位核对 · ACCEPT · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-29T15:59Z

Head: 19f4cf83abf7a7e827614166658cabbc9f9bceab · card #27525 · claim 6495221889, amended twice (Metadata-protocol: added and teaching text; then platform-core.json)

  1. 形态: a draft PR to main carrying Fixes #27525: 36 paths (+531 / −108), inside the claim's surface as amended. The spec accepts a report nav item without reportName and the console draws the index only where the navigation declares it. Both inspectors write the absence of the key, and the teaching text (os explain app, ui/apps.mdx, ui/reports.mdx) and the QA ledger follow. check-governed-merges reports NOT governed (0 of 37 paths), size 639 lines, so the ordinary queue landing applies.
  2. 前提实测: the triage contract review (6498961917, PASS, no must-fix) judged the spec face on dd0bd483. The seat read the PR's own delta dd0bd483 → 19f4cf83 (two main merges excluded): 10 files, none under packages/spec/src or a governed path. (a) The metadata-admin combo gains emptyLabel, and a report item's empty pick calls patch({}, ['reportName']), which removes the key as the Studio inspector does; this answers the review's '' should-fix, pinned by AppNavInspector.reportIndexClear-27525.test.tsx (4 cases). The new i18n keys exist in en and zh. (b) The os explain app row, two docs pages and two QA rows teach the index form; the cli changeset is minor with the PR's declarations.
  3. 门禁 check-runs (on this head): 69 runs read at 2026-10-29T15:59Z: 60 success, 9 skipped, none red or pending. Dev-side: cli 529 files, inspectors 205/205, dispatch-gates --ran 111/111 with 0 NOT-MEASURED, check-changeset-no-major with body r1 exit 0.
  4. 接受的偏差: the mobile tab bar (AppSchemaRenderer) takes no label resolver, so a label-less index entry shows its id there, as every other label-less entry already does in that host. The review graded it cosmetic; the PR body names it.
  5. 范围外去向: four governed skills rows still teach only the named form; they stay true and are carried by card skills: objectstack-ui teaches only the named report navigation item, not the report-index form #27566 adds #27609. Skills-docs: none edited.
  6. 落地条款: the seat checked the prose faces against the diff on this head: the PR body (r1), the six changesets and the teaching text. The seat readies the PR and arms auto-merge in this act, follows it to MERGED, and lands spec: a navigation item of type 'report' without reportName targets the app's report index; the console stops injecting the "All reports" row; ⌘K finds reports from the report registry #27525.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

#27345: the 维护者速读 went out on a head no PASS names · 2026-10-29T16:08Z

⛔ Not a claim, and not a review: triage takes no contract reviews now. The review seat does.


Generated by Claude Code

Merged via the queue into main with commit bf658d1 Oct 29, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-1iqx7w branch October 29, 2026 16:23
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 19f4cf8 · 2026-10-29T16:25Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 19f4cf83abf7a7e827614166658cabbc9f9bceab
Local-runs: none
Scope: packages/spec/src/ui/app.zod.ts + packages/spec/src/ui/app.test.ts, packages/spec/src/stack.zod.ts, packages/lint/src/validate-nav-target-refs.ts, packages/spec/authorable-surface.base.json, content/docs/references/ui/app.mdx, ui/packages/layout/src/NavigationRenderer.tsx, ui/packages/app-shell/src/hooks/useNavTargetLabel.ts, ui/packages/app-shell/src/chrome/CommandPalette.tsx, ui/packages/app-shell/src/views/appReportIndex.ts, ui/packages/app-shell/src/layout/UnifiedSidebar.tsx, ui/packages/app-shell/src/views/metadata-admin/inspectors/AppNavInspector.tsx, ui/packages/app-shell/src/views/metadata-admin/inspectors/InspectorComboField.tsx, ui/packages/app-shell/src/views/metadata-admin/inspectors/AppNavInspector.reportIndexClear-27525.test.tsx, ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx, ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.tsx, ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, ui/packages/app-shell/src/views/studio-design/navSurface.ts, ui/packages/i18n/src/namespaces/studio/en.ts, ui/packages/i18n/src/namespaces/studio/zh.ts, packages/cli/src/commands/explain.ts, content/docs/ui/apps.mdx, content/docs/ui/reports.mdx, docs/qa/platform-checklist/areas/platform-core.json, .changeset/27525-report-nav-index-cli.md, .changeset/27525-report-nav-index-app-shell.md

Delta review of head 19f4cf83abf7a7e827614166658cabbc9f9bceab against record 6498961917 (PASS, head dd0bd483ff19e86361f5546ea86318bc76731327, reviewer session_01AavokzJ5DndAwitDXvKy4U). Sections the move does not touch stand as judged there and are named so below.

Inputs read: card #27525 body and all seven comments (claim 6495221889, os-dev-report 6497652725, claim amendments 6497681948 and 6501227462, ACCEPT pointer 6501763201, Landed 6502297092); PR #27566 body, file list (36 paths, +531/-108) and the net diff against main; PR comments 6497645178 (docs drift), 6498755609, 6498961917 (the previous record, read in full), 6501729633 (seat ACCEPT), 6501922340 (#27345 return-to-draft), 6502331750 (this claim); the 71 check-runs on 19f4cf83: all completed, 62 success, 9 skipped (Auto Label and Check PR Size duplicates, UI Published Artifacts, Temporal Conformance, the Dogfood matrix row and Dogfood Verify CLI, Schedule gate, 18.x upgrade harness, Packed-tarball smoke — opt-in or matrix-conditional rows), none in_progress, none failed. Ruling-ref on the card: none. The repo at /home/claude/objectstack was read with git show, git diff and git grep against the two heads and origin/main 72b08eec only.

What moved since dd0bd483 — three commits, separated:

git diff dd0bd483 19f4cf83 -- packages/spec/src/ui/app.zod.ts packages/spec/src/ui/app.test.ts packages/spec/src/stack.zod.ts packages/lint/src/validate-nav-target-refs.ts packages/spec/authorable-surface.base.json content/docs/references/ui/app.mdx ui/packages/layout/src/NavigationRenderer.tsx ui/packages/app-shell/src/{console,hooks,chrome,layout,views/studio-design}/... ui/packages/types/src/... is empty: the hits file and every file record 6498961917 cites are byte-identical on the two heads. Re-read on the head: ReportNavItemSchema at app.zod.ts:684-691 (reportName: z.string().optional() inside strictObject(navItemSurface('report')), describe names the index), navItemSurface variant !== 'report' guard at :209, stack.zod.ts:4595 typeof nav.reportName === 'string', lint strName / continue at validate-nav-target-refs.ts:125-126. All hold.

① Derived judgments

Items 1 through 13 of record 6498961917 stand as judged there: the hits file and every consumer they read are unchanged on this head (diff empty, above). Re-judged afresh, the move adds:

  1. @object-ui/app-shell metadata-admin write path — AppNavInspector.tsx:458-463: the report combo gets emptyLabel on navType === 'report' only, and onCommit('') on a report item calls patch({}, ['reportName']), whose omit loop (:273-282, for (const key of omit) delete next[key]) removes the key from the written item; every other type keeps patch({ [targetKey]: v }). The written shape for the index is now { id, type: 'report', label? }, the shape the spec accepts and stack.zod.ts skips, the same shape the Studio inspector writes. No accept-set change; a consumer behaviour change that follows the spec's absence rule. Right.
  2. InspectorComboField surface — new optional prop emptyLabel?: string on InspectorComboFieldProps (:128-136); omitted, the combo behaves as before (trigger text falls to loading/placeholder, no empty row, CommandEmpty shown). Given, the list leads with a __empty__ row that commits '' and the trigger shows the label while value === '' (:190-192, :257-264). Additive on an in-package (unexported from the package index, metadata-admin internal) component; the only caller passing it is AppNavInspector. Right; carried under the app-shell minor already declared.
  3. @objectstack/cli public surface — explain.ts:226: the navigation row of os explain app ends its report target (reportName) with "(omit it on a report item to open the app's report index)". Help text only; no command shape moves. On dd0bd483 the row said every item "needs" its target key, which the spec no longer requires of report — the previous record's ③ item 2 should-fix. Now true. Right.
  4. Docs — content/docs/ui/apps.mdx:173-186 (Report Navigation: the index form, { id: 'nav_reports', type: 'report', icon: 'bar-chart-3' }, "The console draws no index entry of its own", the label fallback paragraph at :308-309) and content/docs/ui/reports.mdx:303-306 match the describe and the console on this head (resolveHref → BASE/reports; useNavTargetLabel → sidebar.allReports; ⌘K reads the registry). Prose, not reviewed; read, and nothing it says makes a refused shape. Build Docs and Check Documentation Links green.
  5. QA ledger — platform-core.json nav-surfaces-render r8 (steps[2], acceptance[1] clause and verify) and builtin-apps-nav-render r10 (steps[9]) now say the index is a served, declared entry and that an "All reports" row in Setup or Account is a finding; each carries a history row naming spec: a navigation item of type 'report' without reportName targets the app's report index; the console stops injecting the "All reports" row; ⌘K finds reports from the report registry #27525. Matches UnifiedSidebar.tsx on the head (no injected block) and examples/app-showcase nav_report_index. Not a governed path; check:platform-checklist inside Lint & Repo Gates green.

② Semver level

Record 6498961917's ② stands for the five changesets it judged (@objectstack/spec, @object-ui/layout, @object-ui/app-shell, @object-ui/i18n, @object-ui/types, all minor, each Clause-②: yes (widening) / Metadata-protocol: added; the app-shell one carries the migration line). The move adds a sixth and edits one:

  • .changeset/27525-report-nav-index-cli.md — born at 3406b52f as @objectstack/cli: patch / Clause-②: no / Metadata-protocol: none, regraded at 19f4cf83 to minor / yes (widening) / added. AGENTS.md: "The level follows the kind alone: a kind takes at least minor on a package the diff moves". The PR declares added, the diff moves @objectstack/cli (packages/cli/src/commands/explain.ts), so minor is the floor; the patch grade would have been the gate's discharged residual, not a refusal, and the regrade removes the residual. The changeset body says so. Right. The declaration lines on it agree with the PR body and the other five (the gate's contradiction arm — added under Clause-②: no — is what the first spelling would have been had the body's PR-wide line been read against it; the regrade removes that too).
  • .changeset/27525-report-nav-index-app-shell.md — one sentence added on the metadata-admin inspector (Report index row; picking it removes reportName). Describes item 14. Level unchanged. Right.

Clause-②: yes (widening) — still matches the diff on this head: the report variant's accept set widens, nothing an author wrote parses differently, and the move adds no accept-set change (items 14-18 are consumer behaviour, help text, docs and a QA ledger). Both Check Changeset runs on 19f4cf83 green.

③ Boundary flags

Record 6498961917's ③ items 1, 3 and 4 stand. Re-judged where the move touches them:

  • Item 2 (teaching text) — was escalated as a should-fix: the claim amendment 6497681948 had widened the file surface to explain.ts, apps.mdx, reports.mdx but dd0bd483 did not carry them. 3406b52f lands all three (① items 16-17). The card record and the PR now agree. Answered.
  • New surface addition (claim amendment 6501227462): docs/qa/platform-checklist/areas/platform-core.json, two rows that expected the injected row. Landed in 3406b52f (① item 18); the seat declared it before the review. Answered.
  • Skills' index form (record 6498961917 ③, should-fix follow-up): skills/objectstack-ui/SKILL.md:171, rules/navigation.md:38, skills/objectui/guides/app-composition.md:21,34 are untouched by the PR's own commits (the main merge moves inventory counts in other skills files only); they still teach a named report entry and stay true. Carried by card skills: objectstack-ui teaches only the named report navigation item, not the report-index form #27566 adds #27609 per the Landed comment; Skills-docs: none edited on the PR body is accurate. Escalated to skills: objectstack-ui teaches only the named report navigation item, not the report-index form #27566 adds #27609, as before; not a FAIL.
  • open_questions: empty in the os-dev-report; no new dev flag was raised for the two later commits. The PR body's Round 2 paragraph names them (85 and 34 derived commands; the branch-wide list at 111) — the check-runs on the head are the record for those families, and they are green.

④ Design judgment

packages/spec/src/ui/app.zod.ts — byte-identical to the file record 6498961917 judged; its ④ (holds against 「加上才有」 / Target 1 and the first consumer resolveHref; strictness kept; ADR-0087 widening; the three save paths and the lint agree on absence) stands as judged there, re-read on the head and found as cited. Judged afresh where the move bears on the schema's contract:

  • The '' should-fix (record 6498961917 ④, first break attempt) named three remedies; the PR takes the third: the metadata-admin combo now removes the key on the empty pick instead of writing '' (AppNavInspector.tsx:460-461, pinned by AppNavInspector.reportIndexClear-27525.test.tsx case 1, hasOwnProperty('reportName') === false; CONTROL cases pin a named pick and a page combo with no empty row). Both inspectors now produce one shape for the index face, the one the schema accepts and stack.zod.ts skips. What remains is a hand-written reportName: '': the spec accepts it (unchanged, out of this card), defineStack refuses it when the app declares any report, the lint skips it, the console reads it as the index. No tool in the tree writes it any more, so the ambiguity is reduced to an authored literal that validate-* already refuses in the common case. Residual should-fix: one sentence on the describe (or .min(1) under a later narrowing card) — not a must-fix.
  • Tried to break the new combo: (a) emptyLabel with a non-empty search that matches nothing and allowCustom left at its default: the Use «text» row shows, so the list is never silently empty in AppNavInspector's use; with allowCustom={false} the !emptyLabel guard at :256 would hide CommandEmpty and the !trimmed guard at :257 would hide the index row, leaving an empty list — no caller passes that combination; cosmetic, should-fix at most, outside the hits. (b) The trigger shows emptyLabel while loading and value === '' — correct, since an empty value IS the index whatever the catalogue state. (c) value === '' and a report whose value is '' cannot collide: InspectorComboOption.value is a report name, which ReportSchema requires non-empty. (d) The hint at :492 reads !node.reportName, so an authored '' also shows the index hint while AppPreview.tsx:115 answers reports for it — consistent with the console. (e) A readOnly inspector disables the combo; the index row cannot be committed. No finding.
  • Teaching text vs the schema: explain.ts:226 now states the index form; apps.mdx and reports.mdx state both targets and the console's non-injection — all three say what the describe says, nothing more (none promises a label, a route or a ⌘K behaviour the tree does not have). No finding.
  • Mobile tab bar (record 6498961917 ④): unchanged on this head; cosmetic, outside the spec file. Stands.

Must-fix: none. Should-fix: the residual '' describe sentence; the allowCustom={false} + emptyLabel empty-list corner in InspectorComboField (no live caller); the skills' index form (card #27609).

Implemented-by: claude/project-thread-1iqx7w
Reviewed-by: session_01L6bhgV4fMGJHSdSdx3u9mK

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:ui size/l tests tooling

Projects

None yet

2 participants