You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
console: switching organization can land a member on a chrome-less "no apps" page — resolveLandingPath ignores per-user app access #4473
Triage note: the clean "distinguish no-access from not-published" half of this depends on the backend envelope objectstack-ai/objectstack#8013 (in flight, the backend half of #4252). The fallback route (app surface bounces to /home instead of rendering chrome-less) is implementable today — claiming seat's call.
Switching the active organization (and accepting an invitation, which switches as its last step) can land the user on a page with no header, no navigation and no workspace switcher — there is no way back except editing the URL by hand.
Repro
User A (owner of workspace 甲) invites user B as member.
B accepts the invitation, then creates a workspace of their own (B's workspace, where B is owner).
As B, from /_console/home of B's workspace, open the workspace switcher → select 甲.
Actual
B lands on /_console/apps/setup and sees a bare page:
尚未配置应用 — 当前没有任何已注册的应用。请创建您的第一个应用,或前往系统设置进行配置。
document.querySelectorAll('header').length === 0 — no top bar, no workspace switcher, no way to switch back. The same page is reached right after accepting an invitation (AcceptInvitationPage calls navigate('/home'), but the org switch triggers a full-page load to the console root, which re-resolves the landing).
/_console/home in the very same state renders correctly: top bar, workspace switcher, and a friendly empty state ("还没有应用 — 工作区正在准备中——管理员共享给你的应用会显示在这里。"). So the landing target is the problem, not the workspace.
Expected
Switching into an organization never strands the user. If the resolved landing app is not accessible to the current user in the target org, fall back to /home.
Cause
WorkspaceSwitcher does a full-page navigation to the console root so RootLandingRedirect can resolve the landing (packages/app-shell/src/layout/WorkspaceSwitcher.tsx:97). resolveLandingPath then picks the app purely from app metadata, with no notion of whether the current user may enter it:
In a fresh workspace com.objectstack.setup is the only visible app, so rule 2 sends every user to /apps/setup. A member has no access to it (sys_permission_set reads 403 for positions [org_member, everyone]), and the app surface renders the chrome-less empty state above.
Note the resolver is documented as a product decision ("a build/dev-time PRODUCT decision, declared in metadata — not a runtime, per-tenant preference"). That is fine for choosing which app a product lands in; the gap is that the result is not checked against the current user's access before redirecting.
Suggested fix
Either filter the candidate list by the user's app access before applying rules 1–2, or make the app surface itself bounce to /home (with the shell chrome) when the app is not accessible, instead of rendering a chrome-less empty state. The second also covers a hand-typed /_console/apps/<name> URL.
Environment
Local dev server http://localhost:8080, multi-org enabled, zh locale, observed 2026-08-12. The server was started by the maintainer and its exact commit is not verified; the source quoted above is the objectui main checkout at filing time.
Ruling: land the today-implementable half, per the triage note. The invariant: switching into an organization never strands the user — the app surface bounces to /home (with shell chrome) when the resolved app is not accessible, which also covers hand-typed /_console/apps/<name> URLs. The resolver's product-decision character stays untouched (it still picks the app from metadata); the guard is at the surface. The clean "filter candidates by per-user access / distinguish no-access from not-published" half stays gated on the backend envelope (objectstack#8013, tracked under #4252) — record the dependency in the PR body; if the bounce turns out to require that envelope too, STOP and report. Red-first: reproduce the chrome-less state (member + inaccessible landing app → document.querySelectorAll('header').length === 0 today), assert post-fix the user lands on /home with chrome present; pin the direct-URL case too.
Grading: patch (apps/console and/or @object-ui/app-shell by where the bounce lands — measured, .d.ts-checked).
Surface (mutual exclusion): apps/console/src/components/RootLandingRedirect.tsx + the app-surface component that renders the chrome-less empty state + tests + changeset. Disjoint from in-flight #4472 (organizations/manage), #4463 (views/ObjectView), #4431, #4471. Do NOT touch WorkspaceSwitcher.tsx (its full-page navigation is correct) unless measurement proves the bounce cannot live at the surface — report first if so.
Activity
yinlianghui commentedon Aug 12, 2026
CLAIM — session
session_017Qqyix2QcnpUC9XeYVDzx3, branchclaude/issue-4473-landing-no-strand.Ruling: land the today-implementable half, per the triage note. The invariant: switching into an organization never strands the user — the app surface bounces to
/home(with shell chrome) when the resolved app is not accessible, which also covers hand-typed/_console/apps/<name>URLs. The resolver's product-decision character stays untouched (it still picks the app from metadata); the guard is at the surface. The clean "filter candidates by per-user access / distinguish no-access from not-published" half stays gated on the backend envelope (objectstack#8013, tracked under #4252) — record the dependency in the PR body; if the bounce turns out to require that envelope too, STOP and report. Red-first: reproduce the chrome-less state (member + inaccessible landing app →document.querySelectorAll('header').length === 0today), assert post-fix the user lands on/homewith chrome present; pin the direct-URL case too.Grading: patch (apps/console and/or
@object-ui/app-shellby where the bounce lands — measured,.d.ts-checked).Surface (mutual exclusion):
apps/console/src/components/RootLandingRedirect.tsx+ the app-surface component that renders the chrome-less empty state + tests + changeset. Disjoint from in-flight #4472 (organizations/manage), #4463 (views/ObjectView), #4431, #4471. Do NOT touchWorkspaceSwitcher.tsx(its full-page navigation is correct) unless measurement proves the bounce cannot live at the surface — report first if so.Generated by Claude Code
Generated by Claude Code
fix(console): an inaccessible landing app bounces to /home instead of…
fix(console): an inaccessible landing app bounces to /home instead of…