Repository navigation
First screen after sign-in is the retired Studio home when the app serves an empty navigation #12079
Description
Found during the objectstack-ai/hotclm#87 browser pass on main @ c31c7e2, @objectstack/* 17.7.0. The maintainer asked for this issue: the page is Studio's old home, retired a long time ago, and nobody should land on it.
What a user sees
pnpm demo, no operator setup, sign in as admin@objectos.ai. The browser goes to /_console/apps/clm?package=app.objectstack.hotclm. The sidebar is empty, and the main pane shows the old Studio home: "Good morning · Studio · Your control center for modeling data, designing experiences, and automating the platform", metadata counters, Quick actions (New Object / New View …), Explore. None of it is about contracts, and nothing says what to do next.
Screenshot: qa/browser-test-17-7/screenshots/02-p1-first-screen-after-sign-in.png on branch claude/issue-87-browser-test-17-7.
Why it happens (read from the 17.7.0 console bundle)
GET /api/v1/meta/app/clm→200,"navigation": []for the dev admin. Every HotCLM group is gated on aclm_*.accesscapability (src/apps/clm.app.ts), and the dev admin holds noclm_*set (README, "The dev admin is deliberately none of them").- The console's app index route resolves its landing page from the navigation and falls back to the Studio home when nothing resolves. In
@objectstack/console/dist/assets/index-ocmkyCt6.js:So any app whose navigation is empty for the current user lands onpath:`/`, element: (()=>{ let e = Wv(R,{currentUserId:s?.id??null}); return e ? <Navigate to={e} replace/> : <kv/> })() // Wv(e,t) = Hv(e?.navigation||[], t) // kv = lazy(import('./metadata-admin-CEhVmXFv.js').StudioHomePage)
StudioHomePage. HotCLM is not the only app this can happen to.
Producer
- Platform (console): the empty-navigation fallback renders a retired page. It should show an app-level empty state instead, for example "You have no access to anything in HotCLM yet: ask an administrator for a permission set", or send a platform admin to Setup. This belongs upstream in
objectstack-ai/objectstack. This session could not reach that repo, so the issue is filed here for the seat to forward. - App (contributing): the dev admin gets
navigation: []by design. That design question is open in [Decision]pnpm demo开箱即用时没有任何账号能打开「我的合同」——要不要让脚本建账号,与 #11 第一问耦合 hotclm#28 and [Decision]DESIGN.md§04 makesclm_requesterevery employee's default, but a set that grantsclm_requester.accesscannot bind toeveryonehotclm#11, so this issue does not decide it.
Repro
rm -rf .objectstack/data && pnpm demo- Sign in as
admin@objectos.ai/admin123 - You land on the Studio home with an empty sidebar.
Activity
objectstack-fleet commentedon Oct 10, 2026
Routing · 2026-10-10T02:19Z — moving this card to
objectstack-ai/objectui.The producer named in the body is the Console's app index route (
@objectstack/consoleis built from objectui's console / app-shell): when the current user's navigation resolves no landing page it renders the retiredStudioHomePage. Issues live in the repo where the fix lands, and Console defects land in objectui. The maintainer's instruction for platform problems found in the objectstack-ai/hotclm#87 browser test, verbatim, 2026-10-10: 「你遇到的平台问题应该提交issue」.The app-side half (the dev admin has no
clm_*set, so HotCLM's navigation is empty for that account) stays with hotclm's decision cards objectstack-ai/hotclm#28 and objectstack-ai/hotclm#11 and is not part of this card.Dedupe on
objectstack-ai/objectuibefore moving: MCPsearch_issuesapp index empty navigation falls back to Studio home page StudioHomePage→ 10 hits, none on this (nearest #11863, closed — the/studiolanding header).Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Triage: first grade,
bug·priority:p2·domain:ui·area:studio·pm:queueTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T03:55Z. ⛔ Not a claim, ⛔ not a dispatch.main1b2d0160fe:packages/app-shell/src/console/AppContent.tsx:1084readslanding ? <Navigate …/> : <StudioHomePage />. Any app whose navigation is empty for the current user still lands on the retired Studio home./studioitself is untouched.clm_*sets is hotclm's decision ([Decision]pnpm demo开箱即用时没有任何账号能打开「我的合同」——要不要让脚本建账号,与 #11 第一问耦合 hotclm#28, [WIP] Update documentation for project #11).StudioHomePage. Control: an app with a landing page still navigates to it.objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 1
Session:
session_01B1gHb9baeX7oioD5sHVm7zAccount:
marchtianBranch:
claude/issue-12079-empty-nav-landingWorktree:
objectui-issue-12079Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/app-shell/src/console/AppContent.tsx(the app index route's landing fallback only), a new or reused app-level empty-state component underpackages/app-shell/src/, its tests, and locale entries inpackages/i18n/src/locales/(stop on breach; explain in the report)Container & model:
M(small, but the empty state is a design judgment, not a mechanical edit),mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Responsibility:
objectui's app-shell console AppContent.tsx app index route, which renders the retired StudioHomePage when the navigation resolves no landing page | none: no platform empty state exists for an app the caller has no access in | any signed-in user whose app navigation resolves empty, measured on objectstack-ai/hotclm#87 (17.7.0) for the dev admin; used today by every capability-gated appThread-read: 6093511804
Serial constraints cleared: none. No in-flight card touches
packages/app-shell/src/console/AppContent.tsx. (Corrected: an earlier version of this line named objectui#7611 as dispatched in this round. That card is reserved for the epic lane of objectstack-ai/objectstack#15194,6095619495, and this seat yielded it in6095645635. Its surface isapps/console/src/AppContent.tsx, a different file.)Clause-② is declared
yesbecause the empty state's copy will add published locale keys. The in-seat review needs that reason.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{
"issue": 12079,
"status": "done",
"branch": "claude/issue-12079-empty-nav-landing",
"pr": "#12091",
"session": "session_01B1gHb9baeX7oioD5sHVm7z (subagent: the dispatching seat's session; the container's CLAUDE_CODE_REMOTE_SESSION_ID resolves to the same id, and the relay stamped it on every write)",
"premise_still_valid": true,
"summary": "The app index route in packages/app-shell/src/console/AppContent.tsx now handles three cases. If a landing resolves, it navigates there. Otherwise, if the app serves no navigation (appServesNoNavigation: no item at the top level or in any area, judged by the sidebar's own hasVisibleNavigationItems), it renders AppNoAccessEmptyState. Only in the remaining case does it render StudioHomePage. AppNoAccessEmptyState names the app and offers 'Open Setup' at resolveSetupAppPath(apps) to a caller useWorkspaceAdminStatus confirms as an admin; everyone else is asked to contact an administrator. The PR also adds four empty.* keys to all ten @object-ui/i18n packs and a patch changeset. The fallback was split on navigation emptiness, not on 'no landing', because the Studio app (/apps/studio) reaches the same branch with a full navigation of component items that findFirstRoute does not walk, and the sidebar's 'Studio home' entry links there. So Studio is unchanged, per triage's '/studio itself is untouched'. One conflict is said rather than resolved: the card body quotes the maintainer calling StudioHomePage retired, but on origin/main it is the live root of /apps/studio. Triage's ruling governs and this PR keeps that page; retiring it would be a separate card. It is not raised as an open question because nothing on this card waits on it.",
"mechanism_assumptions": [
"1 CONFIRMED with a correction. AppContent's index route is the only place StudioHomePage renders; its other references are its lazy import, the metadata-admin barrel export, its own tests and five test mocks. But the fallback had a second, live consumer: the Studio app. That is why the split is on navigation emptiness.",
"2 CONFIRMED. Reused useWorkspaceAdminStatus().isAdmin, which reads the active membership role, user.role, user.isPlatformAdmin and the org_owner/org_admin entries in positions. AppContent already reads it for the no-app empty state's Setup button (non-admins are bounced home) and UnifiedSidebar reads it for Home's administration links. Its isResolved half already gates AppContent's render. The URL comes from resolveSetupAppPath(apps), the /setup deep-link policy.",
"3 MEASURED. Eager closure 3244687 to 3244998 bytes gzipped (+311) and +1554 bytes raw; 3168.6 KB to 3168.9 KB; headroom 35.9 KB to 35.6 KB; 290 of 2474 chunks both times. By chunk: index +283 gz / +1232 raw; i18n-locale-en +68 / +322; ui-components +8 / 0 raw; every other chunk -48 gz / 0 raw in total, which is hash renaming. A first cut that used EmptyHeader/EmptyMedia/EmptyContent added +46 gz / +194 raw to ui-components (headroom 3.1 KB), because those primitives have no other caller in the console build. Commit 02569f3 drops them.",
"4 CONFIRMED. Reused Empty, EmptyTitle and EmptyDescription from @object-ui/components, the same three that RouteNotFound and the app-verdict screens compose, plus Button and the Lock icon AppHeader already loads. No new page was written."
],
"clause_2": "yes, as the claim declared: four new keys in the published @object-ui/i18n packs (empty.appNothingAvailable, empty.appNothingAvailableDescription, empty.appNothingAvailableGrantDescription, empty.openSetup). No package export was added and app-shell src/index.ts is unchanged. The PR body carries 'Clause-②: yes' at the start of its own line.",
"files_changed": [
".changeset/12079-empty-nav-landing.md",
"packages/app-shell/src/console/AppContent.tsx",
"packages/app-shell/src/console/AppNoAccessEmptyState.tsx",
"packages/app-shell/src/console/appServesNoNavigation.ts",
"packages/app-shell/src/console/tests/AppContent.emptyNavigationLanding-12079.test.tsx",
"packages/i18n/src/locales/ar.ts",
"packages/i18n/src/locales/de.ts",
"packages/i18n/src/locales/en.ts",
"packages/i18n/src/locales/es.ts",
"packages/i18n/src/locales/fr.ts",
"packages/i18n/src/locales/ja.ts",
"packages/i18n/src/locales/ko.ts",
"packages/i18n/src/locales/pt.ts",
"packages/i18n/src/locales/ru.ts",
"packages/i18n/src/locales/zh.ts"
],
"gates": [
{
"gate": "type-check app-shell + i18n (with the ^build closure)",
"command": "pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2",
"head": "02569f3",
"verdict": "exit 0, Tasks 31 successful of 31 (28 cached); lock VERDICT command-exit 0"
},
{
"gate": "vitest: 14 AppContent suites in app-shell, the 3 apps/console suites that mock StudioHomePage, and defaults-maps-mirror-en-pack",
"command": "pnpm exec vitest run --maxWorkers=2 FILES (from the repo root)",
"head": "02569f3",
"verdict": "Test Files 18 passed (18), Tests 185 passed (185)"
},
{
"gate": "vitest packages/i18n/ (includes all-locales-key-parity)",
"command": "pnpm exec vitest run --maxWorkers=2 packages/i18n/",
"head": "02569f3",
"verdict": "Test Files 82 passed (82), Tests 1316 passed, 13 skipped (skips predate this change)"
},
{
"gate": "check:eager-closure",
"command": "cd apps/console and CI=1 pnpm exec vite build, then pnpm check:eager-closure; before at 023f00d, after at 02569f3",
"head": "02569f3",
"verdict": "exit 0; before 3168.6 KB (headroom 35.9 KB), after 3168.9 KB (headroom 35.6 KB); +311 B gz, +1554 B raw"
},
{
"gate": "check:eager-locale-catalogues",
"command": "pnpm check:eager-locale-catalogues",
"head": "02569f3",
"verdict": "exit 0; en resident 44.2 KB to 44.3 KB"
},
{
"gate": "check:i18n-keys",
"command": "pnpm check:i18n-keys",
"head": "02569f3",
"verdict": "exit 0; every call-site key resolves, inline defaults match en, arguments match holes"
},
{
"gate": "check:i18n-drift",
"command": "pnpm check:i18n-drift",
"head": "02569f3",
"verdict": "exit 0; 0 en values changed, 4 keys added"
},
{
"gate": "check:i18n-dead-keys",
"command": "pnpm check:i18n-dead-keys",
"head": "02569f3",
"verdict": "exit 0 (report-only); none of the four new keys is a candidate"
},
{
"gate": "check:new-line-citations",
"command": "pnpm check:new-line-citations",
"head": "02569f3",
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s)"
},
{
"gate": "check:control-bytes",
"command": "pnpm check:control-bytes",
"head": "02569f3",
"verdict": "OK, 8286 tracked text files scanned"
},
{
"gate": "check:phantom-deps / self-import / unreferenced-sources / esm-specifiers",
"command": "pnpm check:NAME, one per gate",
"head": "02569f3",
"verdict": "exit 0 each"
},
{
"gate": "check:vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape / test-path-roots",
"command": "pnpm check:NAME, one per gate",
"head": "02569f3",
"verdict": "exit 0 each"
},
{
"gate": "changeset-presence / changeset-no-major / changeset-claims / pending-changeset-literals",
"command": "node scripts/check-changeset-presence.mjs; node scripts/check-changeset-no-major.mjs; pnpm check:changeset-claims; pnpm check:pending-changeset-literals",
"head": "02569f3",
"verdict": "exit 0 each; presence: 14 source files of 2 released packages changed, 1 changeset declared"
},
{
"gate": "governed-queue-guard",
"command": "node scripts/check-governed-queue-guard.mjs --test CHANGED_PATHS",
"head": "02569f3",
"verdict": "NOT GOVERNED (15 paths)"
},
{
"gate": "eslint on the touched files (narrowed)",
"command": "pnpm exec eslint --no-inline-config --format json FILES",
"head": "02569f3",
"verdict": "14 files, 0 errors, 33 warnings, all in AppContent.tsx; the base AppContent.tsx read via --stdin also has 33. Population: root eslint.config.js, which sets no parserOptions.project or projectService, and no eslint-rules/ rule reads another file. So per-file verdicts cannot move for untouched files."
},
{
"gate": "reverse verification (ablation)",
"command": "node /home/user/objectstack/scripts/ablation-replace.mjs --file packages/app-shell/src/console/AppContent.tsx --anchor 'if (appServesNoNavigation(activeApp)) {' --replacement 'if (false && appServesNoNavigation(activeApp)) {' -- pnpm exec vitest run --maxWorkers=2 THE_NEW_TEST_FILE",
"head": "02569f3",
"verdict": "direction red as predicted. Mutation landed: anchor 1 to 0, blob adafa25a59b2 to 29b121a1c8cd. Tests: 4 failed, 4 passed (the 4 empty-state tests fail and the DOM shows data-testid studio-home-page; the 2 controls and 2 predicate tests stay green). Restored: blob equals HEAD, git diff HEAD empty, re-run 8 passed. The subject is resolved from src by a relative import, so no dist build is involved."
},
{
"gate": "NOT RUN locally (owned by CI)",
"command": "pnpm lint (repo-wide), full pnpm test, Bundle Analysis workflow, E2E",
"head": "02569f3",
"verdict": "declared to CI"
}
],
"line_budget": "not applicable: no skills/** file, governed ledger or line-ratcheted file is touched.",
"deviations": [
"File surface: the claim named one empty-state component; a second new module, packages/app-shell/src/console/appServesNoNavigation.ts, holds the predicate. Keeping it out of the component file keeps that file component-only for react-refresh/only-export-components (the first cut drew one new warning).",
"Test invocation: the repo-root form 'pnpm exec vitest run --maxWorkers=2 FILE' instead of the dispatch's 'pnpm --filter PKG exec vitest run'. objectui AGENTS.md section 9 names the package-level cwd as a false-green trap.",
"Type-check ran through 'pnpm turbo run type-check --filter=...' so that turbo's ^build dependency built the upstream closure first; the bare 'pnpm --filter PKG type-check' needs those dists present.",
"Commit trailers: the model-free pair (Claude-Session plus Co-authored-by: Claude) per objectui AGENTS.md, not the harness reminder's model-named Co-Authored-By line.",
"Lock: three calls ended in queue-timeout exit 99 (NOT MEASURED, nothing ran) and were re-queued under the same slot. Every verdict above comes from a run that acquired the lock and printed VERDICT command-exit 0.",
"Live browser pass: NOT MEASURED. It needs a backend serving an app whose navigation is empty for the signed-in user (the hotclm#87 setup). Routing is pinned in RTL against the real AppContent and router."
],
"tests": "New file packages/app-shell/src/console/tests/AppContent.emptyNavigationLanding-12079.test.tsx, 8 tests, all passed at 02569f3: empty navigation renders the empty state and not StudioHomePage, names the app and stays on /apps/clm; a non-admin gets no Setup link; an admin gets the link at /apps/com.objectstack.setup; a member whose list carries Setup still gets no link; control: an object landing still navigates to /apps/clm/contract; control: a Studio-shaped app (component items only) still renders StudioHomePage; two unit tests on appServesNoNavigation. Surrounding suites: 18 files and 185 tests passed; i18n 82 files, 1316 passed, 13 skipped. Ablation: 4 failed / 4 passed while mutated, 8 passed restored (see gates).",
"mcp_calls": "0 (no MCP GitHub tool was called)",
"api_writes": "3, each through the fleet-write relay as objectstack-fleet[bot], and each one repository_dispatch to objectstack-ai/objectstack. (1) pr_create: POST /repos/objectstack-ai/objectui/pulls, draft, created #12091; read back 11354 bytes sent and stored, identical. (2) label-write --assign marchtian: POST /repos//issues/12091/assignees; read back shows assignee marchtian, and labels tests, package: app-shell, package: i18n came from labeler and were untouched. (3) this os-dev-report comment: POST /repos//issues/12079/comments. Also three git pushes, which are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed. A non-Studio app whose served navigation is non-empty but holds no item the landing resolver walks (only component, url or action items, or items only under areas) still lands on StudioHomePage. The spec says areas take precedence over the top-level navigation, while resolveLandingRoute reads only the top level. No reach was measured: no producer outside the Studio app is known, the platform apps account and setup both resolve a landing, and the objectstack examples declare no areas. Dedupe words: landing route areas precedence; findFirstRoute component items; StudioHomePage fallback non-empty navigation",
"carrier: 承接者:无 · noted, not filed. StudioHomePage is still the live root of the Studio app (/apps/studio), linked from UnifiedSidebar's 'Studio home' entry, while the card body quotes the maintainer calling it retired. Retiring it would need /apps/studio to get a landing: findFirstRoute walking component items, or Studio declaring one. Nothing on this card waits on that. Dedupe words: StudioHomePage retire; Studio app landing; findFirstRoute component"
]
}
objectstack-fleet commentedon Oct 10, 2026
ACCEPT — PR objectui#12091 (head
02569f377f) ·domain:uiseat 3,session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T12:47ZReviewed against GitHub and the diff, not the report (
6096347163).Shape:
main.Fixes #12079, andClause-②: yessits at the start of a line.marchtian.Scope: 15 files, +473/−6:
AppContent.tsx, two new app-shell modules, one new test file, four keys in each of the ten locale packs, and one changeset. Nothing touchescontent/docs/releases/and no file is unrelated. NOT GOVERNED.The diff against triage (
6093511804):AppNoAccessEmptyState, which names the app. Setup is linked only for the console's existing workspace-administrator verdict, at the/setupdeep-link URL.componentitems, keeps its home./studiois untouched.Contract review:
6097645373,Served-tier: CONTRACT_REVIEW_TIER, PASS on this head. Theyesis carried by four additive i18n keys. No export is added.CI on
02569f377f: 43 runs, 40 success, 3 skipped by design, 0 failed.Eager closure, measured by the dev: +311 B gzipped. Headroom went from 35.9 KB to 35.6 KB.
Changeset prose, checked sentence by sentence against the diff:
useWorkspaceAdminStatus…) gets an 'Open Setup' link, at the URL the/setupdeep link resolves";@object-ui/i18nkeys, in all ten language packs".All match. It is
@object-ui/app-shell: patchwith@object-ui/i18n: patch.Out of scope:
doc-only, or items only underareas) still lands onStudioHomePage. It predates this PR. objectstackorigin/mainhas no app or example declaringareas(zero hits; control:navigation: [hits three apps). It goes to Acceptance notes and is not filed.StudioHomePage(the card body calls it retired) goes to the maintainer in the round report. It is not a condition of this card.Landing: ready + auto-merge through the queue, now.
Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Landed: PR objectui#12091 →
main417e2456·domain:uiseat 3,session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T20:07Zce991bd7, which carries objectui#12100 (the Spec Main Shape Gate fix).b6fc38bb06, includingAppContent.tsx, the two new modules (AppNoAccessEmptyState.tsxandappServesNoNavigation.ts), the ten locale packs and the changeset.02569f377f. Record6101518324is PASS on it.completedby the PR'sFixesline.pm:dispatchedcomes off in the label write after this comment.6097652323.StudioHomePage. The card's acceptance notes carry the residual: non-empty navigation with no walkable landing.Generated by Claude Code