Repository navigation
feat(spec): a report nav item without reportName opens the app's report index; the console stops injecting All reports - #27566
Conversation
…rt index Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu Co-Authored-By: Claude <noreply@anthropic.com>
…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>
… save Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu Co-Authored-By: Claude <noreply@anthropic.com>
…inor Claude-Session: https://claude.ai/code/session_01WGufzhEW5k2a4Vd5KSupfu Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 176 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
…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
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, taking Generated by Claude Code |
Contract reviewServed-tier: 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 ① Derived judgments
② Semver levelFive root changesets, all Clause-②: ③ Boundary flagsDev flags (
Skill teaching (governed, not in this PR's file list): ④ Design judgment
Holds:
Tried to break it:
Must-fix: none. Should-fix: the Implemented-by: 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>
|
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP 维护者速读 (席位终稿)
席位意见:接受。分诊契约复核 PASS(6498961917,head 席位核对 · ACCEPT ·
|
|
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 |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 19f4cf8 · 2026-10-29T16:25Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta review of head 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 What moved since
① Derived judgmentsItems 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:
② Semver levelRecord 6498961917's ② stands for the five changesets it judged (
Clause-②: ③ Boundary flagsRecord 6498961917's ③ items 1, 3 and 4 stand. Re-judged where the move touches them:
④ Design judgment
Must-fix: none. Should-fix: the residual Implemented-by: VERDICT: PASS Generated by Claude Code |
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
reportnav item and all stay true for a named report; none teaches the index form, and none teaches thatreportNameis required:skills/objectstack-ui/SKILL.md:171(thereportrow listsreportName,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: noneis measured asadded. The order says "as measured", and AGENTS.md's kinds put "a new accepted value" underadded:{ type: 'report' }with noreportNamewas refused and is now accepted. The changesets carryadded, and every package the PR moves is gradedminor:@objectstack/spec,@objectstack/cli(help text only, but the PR-wideaddedsets its floor),@object-ui/layout,@object-ui/app-shell,@object-ui/i18nand@object-ui/types.Migration (in the
@object-ui/app-shellchangeset): 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 清单同步改写。{ 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/main57c51aab(before).Spec:
NavigationItemSchema.safeParse({ id: 'nav_reports', type: 'report' }):Console:
UnifiedSidebar.tsxdrew a static row under{!isStudioApp && (…)}after the app'sNavigationRenderer: aLinkto${basePath}/reportswithdata-testid="sidebar-report-index", textsidebar.allReports("All reports" / 「全部报表」), for every non-Studio app, whatever its navigation or its reports. Pinned on main byUnifiedSidebar.reportIndexLink-13020.test.tsx("links the app's report index, though the navigation names no report").⌘K: the card's premise does not hold on main.
CommandPalette.tsxalready 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 bymayOpenReportto the ones whose datasets read objects the member may read, and by the navigation's gates. That is the same listAppReportIndexViewshows. Pinned on main byAppReportIndex-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 at19f4cf83). The same probe answersbare report item: ACCEPTED; the other two lines are unchanged.The spec (
packages/spec/src/ui/app.zod.ts)ReportNavItemSchema.reportNameisz.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 atnavItemSurface(reportname→ "type: 'report' (with reportName)" on every other variant) stays true and is pinned.stack.zod.ts's cross-check of a namedreportNameagainstreportsreadstypeof nav.reportName === 'string', so an absent name skips it; lintvalidate-nav-target-refslikewise skips an absent target. Regenerated:content/docs/references/ui/app.mdx(fivereportNamerows go from ✅ to optional with the new text).check:generatedreports the other 13 artifacts current,check:authorable-surfaceincluded: the base lists keys (ui/ReportNavItem:reportNamestays), not required-ness, so it does not move. No metadata-form row or form translation namesreportName, so no bundle moves.Readers of a nav item's
reportName, measuredreportName, beforelayoutresolveHref(sidebar link, mobile tab bar, ⌘K, Studio preview, active-row election)#, a dead entryBASE/reports; the active-row election reads the same answer, so the entry lights on the index route (exact match) and not onreport/NAMElayoutinheritedNavItemLabel(label-less entry)id{ kind: 'reportIndex' }target, else theidapp-shelluseNavTargetLabel(the console's resolver)sidebar.allReports("All reports" / 「全部报表」), the injected row's own textapp-shellUnifiedSidebarNavigationRendererapp-shellCommandPaletteundefinedin the set of nav-named reports and in the item value)reportNamecounts as nav-named; the value never holdsundefinedapp-shellappReportIndex(reportNamesIn)app-shellAppHeader(breadcrumb)StudioDesignSurface)reportNameAppNavCanvasnavPayloadOf)NAV_TARGET_OPTIONAL); the file's own parity pin (left out exactly when the spec refuses it bare) now judgesreporttaken bareAppNavInspectoremptyLabelonInspectorComboField), and picking it REMOVESreportName, as the Studio does, never writing''; the 「报表索引」 hint shows whilereportNameis absent; its live preview shows/apps/APP/reportsAppPreviewreportsnavSurfacebindTarget(what the Studio rail opens)nullShipped 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.tspinsnavigation[3].children[11]).examples/app-todo(shipstask.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 underpackages/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
packages/spec/src/ui/app.test.ts, replacing "should reject report nav item without reportName"): a barereportitem parses on the variant, throughNavigationItemSchemaand inside anAppSchema, and holds noreportNameafter parse; an unknown key on it is still refused, andreportNameon apageitem still answers "type: 'report' (with reportName)"; the describe names the report index.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/reportshref, nosidebar-report-index); (b) a declared label-less index entry links/apps/crm/reportsnamed "All reports", a labelled one shows its label; (c) it is the active row on/apps/crm/reportsand 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.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 noundefined.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 offersReport indexfirst; a pick writesreportName; the index face removes it again),StudioDesignSurface.sharedSelect-11865(reportBornand a newreportBoundrow on the index face),AppNavCanvas.navPayload-11776(barereporttaken and sent).AppNavInspector.reportIndexClear-27525.test.tsx, new): picking "Report index" on a named report entry writes it with noreportNamekey; the trigger names the index while the key is absent; CONTROL: picking a report writes its name; CONTROL: apageentry's combo offers no empty row. Ablated (the empty pick routed back topatch({ reportName: '' })): the first case goes red, the other three stay green; restored.UnifiedSidebar.iconModeLabelFocus-12795(the tab walk no longer ends on "All reports") andUnifiedSidebar.favoritesMenu-12427(no row to exclude).Runs
All on the shared container, under
scripts/pm/os-verify-lock.sh.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).origin/main(headdd0bd483):build+check:generated→ "All 14 generated artifacts are up to date";test→Test Files 752 passed (752),Tests 22341 passed | 1 todo.@object-ui/layout, the@object-ui/typesnav 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, ondd0bd483:Test Files 138 passed (138),Tests 1755 passed (1755). Narrowed to these files on purpose; CI's Test UI runs the rest.@object-ui/types,@object-ui/layout,@object-ui/i18n,@object-ui/app-shell: all fourtype-check: Done,VERDICT command-exit 0. The fullturbo run build --concurrency=2(115/115 tasks, the console'stscincluded) is green too.Ablation
One ablation, of the behaviour the console pins test:
resolveHref's index answer put back to'#'withscripts/ablation-replace.mjs(ui/packages/layout/src/NavigationRenderer.tsx, anchor hit 1 → 0, blob9f866973→f4b35fdd), then the sidebar and report-index files run (the ui suites alias@object-ui/layoutto 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: blob9f866973== HEAD,git diff HEADempty. 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 assertionmainpinned (UnifiedSidebar.reportIndexLink-13020.test.tsx: "links the app's report index, though the navigation names no report"), so it is red againstmain'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.AppSchemaRenderer) takes no label resolver, so a label-less index entry there shows itsid, as every other label-less entry already does in that host.reportName: ''stays accepted by the spec and refused bydefineStackwhen the app declares reports; this PR does not rule on it. Both inspectors now reach the index by removing the key, so neither writes''.os explain app(packages/cli/src/commands/explain.ts) now says a report item may omitreportNameto open the index;content/docs/ui/apps.mdx(Report Navigation, and the label-fallback paragraph) andcontent/docs/ui/reports.mdxdocument{ type: 'report' }and say the console adds no index entry of its own.docs/qa/platform-checklist/areas/platform-core.jsonstill 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) andplatform-core.builtin-apps-nav-render(r10:steps[9]) now say the index is a declared, served entry, each with ahistoryrow;check:platform-checklistOK. History rows that record the old row are left as written.skills/objectstack-ui/SKILL.md:171,rules/navigation.md:38,skills/objectui/guides/app-composition.md:21,34) is carded separately by seat 3.reporttype on an entry and leaving the report unpicked.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 108 commands at01ed76afand the same 108 at0b6a152e. 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".minorand the merge: every changeset gate in the list again (check-changeset-no-major --base origin/main,check-empty-changesetand 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-protocoladded, and no package … it moves is gradedpatch".gen:authorable-surface-baserefuses on this shallow clone (it cannot establish the anchor's ancestry);check:authorable-surfaceis green insidecheck:generated, and the base, a key list, has no entry this change moves.19f4cf83;origin/main72b08eecmerged atfad30718). The teaching-text paths derive 85 commands, run serially: 83 exit 0, andcheck:docs(exit 1) andcheck:skill-examples(exit 3) refused because the spec build predated the merge; both exit 0 afterpnpm --filter @objectstack/spec build. The inspector paths derive 34, all exit 0. Over the whole branch the list is now 111: the 108 above pluscheck:i18n,check:i18n-walk-parityandcheck: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-shelltype-check: exit 0.!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