Skip to content

First screen after sign-in is the retired Studio home when the app serves an empty navigation #12079

Closed

Description

@zhuangjianguo

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)

  1. GET /api/v1/meta/app/clm → 200, "navigation": [] for the dev admin. Every HotCLM group is gated on a clm_*.access capability (src/apps/clm.app.ts), and the dev admin holds no clm_* set (README, "The dev admin is deliberately none of them").
  2. 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:
    path:`/`, 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)
    So any app whose navigation is empty for the current user lands on StudioHomePage. HotCLM is not the only app this can happen to.

Producer

Repro

  1. rm -rf .objectstack/data && pnpm demo
  2. Sign in as admin@objectos.ai / admin123
  3. You land on the Studio home with an empty sidebar.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    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/console is built from objectui's console / app-shell): when the current user's navigation resolves no landing page it renders the retired StudioHomePage. 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/objectui before moving: MCP search_issues app index empty navigation falls back to Studio home page StudioHomePage → 10 hits, none on this (nearest #11863, closed — the /studio landing header).


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Triage: first grade, bug · priority:p2 · domain:ui · area:studio · pm:queue

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T03:55Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Re-read on objectui main 1b2d0160fe: packages/app-shell/src/console/AppContent.tsx:1084 reads landing ? <Navigate …/> : <StudioHomePage />. Any app whose navigation is empty for the current user still lands on the retired Studio home.
    • Why p2: a signed-in user lands on a page that is not about their app and has no next step. Any app with capability-gated navigation reaches it for a user without grants.
    • Direction:
      • The empty-navigation fallback is an app-level empty state that says the caller has no access in this app yet.
      • For a caller who can grant access, it links to Setup.
      • ⛔ Never the Studio home. /studio itself is untouched.
    • Out of scope: whether HotCLM's dev admin should hold clm_* sets is hotclm's decision ([Decision] pnpm demo 开箱即用时没有任何账号能打开「我的合同」——要不要让脚本建账号,与 #11 第一问耦合 hotclm#28, [WIP] Update documentation for project #11).
    • Pins: an app with empty navigation renders the empty state, not StudioHomePage. Control: an app with a landing page still navigates to it.
  3. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 10, 2026
  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01B1gHb9baeX7oioD5sHVm7z
    Account: marchtian
    Branch: claude/issue-12079-empty-nav-landing
    Worktree: objectui-issue-12079
    Domain: domain:ui
    Seat: domain:ui#3
    File 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 under packages/app-shell/src/, its tests, and locale entries in packages/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 app
    Thread-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 in 6095645635. Its surface is apps/console/src/AppContent.tsx, a different file.)

    Clause-② is declared yes because the empty state's copy will add published locale keys. The in-seat review needs that reason.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    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"
    ]
    }

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR objectui#12091 (head 02569f377f) · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T12:47Z

    Reviewed against GitHub and the diff, not the report (6096347163).

    • Shape:

      • The PR is a draft on main.
      • Its first line is Fixes #12079, and Clause-②: yes sits at the start of a line.
      • No other card number sits near a closing keyword.
      • The assignee is 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 touches content/docs/releases/ and no file is unrelated. NOT GOVERNED.

    • The diff against triage (6093511804):

      • An app that serves the caller no navigation now opens on AppNoAccessEmptyState, which names the app. Setup is linked only for the console's existing workspace-administrator verdict, at the /setup deep-link URL.
      • A landing still navigates.
      • The Studio app, whose navigation is all component items, keeps its home. /studio is untouched.
    • Contract review: 6097645373, Served-tier: CONTRACT_REVIEW_TIER, PASS on this head. The yes is 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:

      • "now opens on an app-level empty state, not on the Studio home";
      • "the app's own label";
      • "A user the console treats as a workspace administrator (useWorkspaceAdminStatus …) gets an 'Open Setup' link, at the URL the /setup deep link resolves";
      • "The Studio app still opens on its own home";
      • "New @object-ui/i18n keys, in all ten language packs".

      All match. It is @object-ui/app-shell: patch with @object-ui/i18n: patch.

    • Out of scope:

      • A non-Studio app with non-empty navigation but no walkable landing (doc-only, or items only under areas) still lands on StudioHomePage. It predates this PR. objectstack origin/main has no app or example declaring areas (zero hits; control: navigation: [ hits three apps). It goes to Acceptance notes and is not filed.
      • Whether the Studio app's own root should keep 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

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Landed: PR objectui#12091 → main 417e2456 · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T20:07Z

    • The merge: through the merge queue. Its one parent is ce991bd7, which carries objectui#12100 (the Spec Main Shape Gate fix).
    • Content check: all 15 files the merge changes are blob-identical to the landing head b6fc38bb06, including AppContent.tsx, the two new modules (AppNoAccessEmptyState.tsx and appServesNoNavigation.ts), the ten locale packs and the changeset.
    • The landing head: a pure base merge of the accepted head 02569f377f. Record 6101518324 is PASS on it.
    • The card: closed completed by the PR's Fixes line. pm:dispatched comes off in the label write after this comment.
    • ACCEPT 6097652323.
    • For the maintainer: the round report asks whether the Studio app's own root should keep rendering StudioHomePage. The card's acceptance notes carry the residual: non-empty navigation with no walkable landing.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions