Uh oh!
There was an error while loading. Please reload this page.
fix(console): an inaccessible landing app bounces to /home instead of stranding the user chrome-less (#4473) - #4483
Merged
Conversation
… stranding the user chrome-less (#4473) Switching organization could land a member on `/apps/setup` rendering the bare "no apps configured" screen: no header, no navigation, no workspace switcher, and no way back except editing the URL by hand. `GET /meta/apps` is filtered per session server-side (`filterAppForUser`), so an empty list means "nothing here is yours to open", not "this workspace has no apps". The app surface now redirects to `/home` — which renders inside the shell chrome and already carries the role-aware copy for this state — whenever it has no app to enter and the viewer is not a workspace admin. A workspace admin keeps the first-run empty state whose CTAs only they can act on; a user with access to the app enters it unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 12, 2026
CollaboratorAuthor
ACCEPT — step-7 复核 by PM session
Flipping ready + arming auto-merge. Slot refills next; the admin-zero-apps chrome-less first-run screen is noted as a product question for the maintainer, not filed (deliberate design with working CTAs). Generated by Claude Code Generated by Claude Code |
yinlianghui
marked this pull request as ready for review
August 12, 2026 19:30
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes#4473
The defect
Switching the active organization (and accepting an invitation, which switches as its last step) could land a
memberon/apps/setuprendering the bare "no apps configured" screen —document.querySelectorAll('header').length === 0: no top bar, no navigation, no workspace switcher, and no way back except editing the URL by hand.Measured chain, end to end:
WorkspaceSwitcher.handleSwitchfull-page-navigates to the console root (window.location.href = resolveRootUrl()) — correct, and untouched here.MetadataProviderseeds the app list fromsessionStorage(objectui:metadata:app, keyed by TYPE only, not by org) and clearsloadingbefore the org-scoped fetch lands, soRootLandingRedirectresolves the landing from the PREVIOUS workspace's list — its single visible app,setup— and rule 2 sends the user to/apps/setup.GET /api/v1/meta/appsis already filtered PER SESSION server-side (filterAppForUser,packages/rest/src/rest-server.ts— see meta apps by-name route: answer an explicit permission-denied envelope for an unauthorized session, instead of being indistinguishable from "not published" (backend half of objectui#4252) objectstack#8013), so for a member with no app access the settled list is empty.AppContent,isSetupRoutesuppressesrequestedAppMissing, the pseudo-route fallback finds nolauncherApps[0], and the!activeAppguard returns a barediv— above the singleConsoleLayoutmount. That early return is why there is no chrome: it is not a route mounted outside the shell, it is a return that precedes the shell.The invariant
Switching into an organization never strands the user. The guard lands at the app surface, so a hand-typed
/_console/apps/:nameURL is covered identically; the resolver (RootLandingRedirect) stays a metadata-only product decision and is not touched.What changed
packages/app-shell/src/console/AppContent.tsx— when the surface has no app to enter and the viewer is not a workspace admin, it returns aNavigateelement to/homewithreplaceinstead of the chrome-less empty state. Router-relative on purpose:Navigateresolves through the host'sbasename, so the/_consolemount is preserved without building a URL by hand (resolveConsoleUrlis for full-page navigations that leave the router).RequireAiSurfaceinConsoleShell.tsxalready bounces the same way for a surface the runtime cannot serve.The role-aware condition is not a stand-in for per-app access — it is what the empty state's own copy already presupposes. "No apps configured — create your first app, or go to system settings" asserts a WORKSPACE-level fact that a per-user-filtered list cannot establish, and offers two actions a non-admin cannot perform. For a workspace admin it stays the deliberate first-run surface (#3573 / #3590); for everyone else
/homeis the honest destination — it renders inside the shell chrome and already carries the role-aware copy for exactly this state ("No applications yet — your workspace is being set up…",console/home/HomePage.tsx), which the issue verified renders correctly in the same state.The half that stays gated (dependency)
Telling "you may not enter THIS app" apart from "this app is not published" per app needs the backend permission-denied envelope, objectstack-ai/objectstack#8013 → console half #4252. Nothing here waits on it: the bounce reads only the per-user-filtered list that ships today, so
/meta/appsreturning an app is the whole "can enter" signal it needs. When #4252 lands, therequestedAppMissingbranch ("App not available — it may still be publishing") is where the denied-vs-unpublished distinction belongs; that branch is deliberately unchanged here.Red-first
New pin
packages/app-shell/src/console/__tests__/AppContent.inaccessibleAppStrand.test.tsx. Againstorigin/main'sAppContent.tsx(taken out withgit checkout, restored after):The failure dump is the strand itself: the
div.h-screen.flex.items-center.justify-centerwrapper carryingempty.noAppsConfigured,create-first-app-btn,go-to-settings-btn— and zeroheaderelements. After the fix, 5 passed (5).The two cases that pass in BOTH directions are the must-not-change ones, and that is the point:
console-layoutwithdata-active-app="crm", URL unchanged);The history assertion uses react-router's
useNavigationType()rather thanwindow.history.back(): underMemoryRouterthe window history object is inert, so ahistory.back()there would have gone green whatever the bounce did.Knock-on to existing pins
Four suites drove the zero-app empty state with
useIsWorkspaceAdmin: () => false; that screen now renders for admins only, so their viewer mock is flipped totrue(with the reason recorded in each file). The subjects those files measure — CTA target resolution (#3573 / #3590), component/metadata routes in the zero-app branch (#3610), pseudo-route segment matching (#3638), the legacy/system/*redirect targets (#3655) — are viewer-independent and their assertions are unchanged.Verification
pnpm exec vitest run packages/app-shell/ apps/console/→ 402 files / 3933 passed, 1 skipped.pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json) → exit 0;pnpm --filter @object-ui/console type-check(tsc --noEmit && tsc -b tsconfig.node.json --force) → exit 0. Dependency closures built first (--filter '@object-ui/app-shell^...' build).dist/console/AppContent.d.tsbuilt fromorigin/mainvs from this branch: byte-identical — no public type surface change, hencepatch.eslinton the six touched files: 0 errors (37 pre-existinganywarnings).node scripts/check-changeset-presence.mjs→ OK (.changeset/landing-app-no-strand-4473.md,@object-ui/app-shellpatch);node scripts/check-control-bytes.mjs→ OK.Generated by Claude Code