Skip to content

fix(app-shell): recovery redirects follow the declared landing (objectui#7373) - #10041

Merged
os-tesla merged 3 commits into
mainfrom
claude/issue-7373-home-redirect-declared-landing
Sep 19, 2026
Merged

os-tesla merged 3 commits into
mainfrom
claude/issue-7373-home-redirect-declared-landing

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7373

app.isDefault declares where a deployment's home is. objectui#7256 (PR #7372) moved the console chrome's four Home affordances onto useHomePath(). The error-recovery exits kept naming the environment launcher literally (ADR-0075), so on cloud's control plane a customer refused an app — or bounced off a surface their runtime does not serve — landed among "Build an app" / "Start from a template" cards that act on an environment their deployment does not have, beside "Your apps" tiles that are the control plane's own internal management apps.

Ruling B on the card ordered the work: measure first, then change. The classification below is the measurement, and it is the reviewable half of this change.

1. The measurement

Population: every /home occurrence in packages/app-shell/src + apps/console/src at 030a675b0, in every quoting. Re-derive it with

git grep -n -E "['\"]/home['\"]" 030a675b0 -- packages/app-shell/src apps/console/src
git grep -l isDefault -- packages/app-shell/src   # lit control: the instrument is live
at 030a675b0
non-test occurrences, single-quoted (the card's own recipe) 13
non-test occurrences, double-quoted 5
non-test, both quotings 18 (3 of them inside comments ⇒ 15 live)
test-side, both quotings 80, across 34 files

Two readings worth having before the table:

  • The card's re-check recipe cannot see the two most important sites. It greps '/home' — single quotes — and both RedirectWithSplash to="/home" redirects are double-quoted. One of them is AppContent's no-app bounce, i.e. the first surface the card names. Anyone re-running that recipe and reading 12 or 13 as "the work" would ship the card's headline case unfixed.
  • The fixture half is not "about a dozen": it is 80 occurrences over 34 files at this denominator (57 by the dispatch's single-quote count). Most are route sinks and start URLs, not destination pins — which the table below is what separates.

2. Classification — every non-test occurrence, with its verdict

MOVED — nine live sites now resolve through useHomePath()

file (at 030a675b0) what it is why it moves
console/AppContent.tsx — the denied screen's Back-to-home button recovery affordance on "you don't have access to this app" the card's own case; the screen renders above the single ConsoleLayout mount, so this button is the whole way back
console/AppContent.tsx — the no-app-to-enter bounce (RedirectWithSplash) recovery redirect the surface the card names first; sits below the readiness gate, so the list it resolves against has settled
console/ConsoleShell.tsxRequireAiSurface's redirectTo default recovery redirect for a runtime with no AI agent only the DEFAULT moved; a host passing redirectTo still wins. No caller in this repo passes one
console/ai/AiChatPage.tsxresolveCollapseToDockTarget's last rung landing when /ai was the entry point triage flagged this conditional for individual thought: rungs 1 and 2 (remembered origin, history back) are unchanged; only "nothing behind us" was a literal. It is now a required parameter, so a new call site cannot reintroduce the literal
console/ai/AiChatPage.tsx — the no-agent screen's Home button recovery affordance same screen family as AppContent's denied case
views/studio-design/StudioDesignSurface.tsx — deleted-package eviction recovery redirect: the package under the editor is gone and no sibling is left destination was a literal; the sibling-package branch beside it is untouched
views/studio-design/StudioDesignSurface.tsx — header Home button Home affordance inside the Studio frame named by the card
apps/console/.../StudioRoute.tsxRequireStudioAccess's redirectTo default recovery redirect: no studio.access the "no access" half of the card, in the console app
apps/console/.../StudioRoute.tsx — the Studio front door's wordmark Home affordance one route from the previous row ⚠️ declared in-place fix, flag it if you disagree. It is the sibling screen of the header Home button above: leaving it would put two different homes one route apart inside one frame, which is the objectui#7256 defect reproduced by this very change

A tenth live site, AcceptInvitationPage, follows the same declaration by a different route — it lands on the console root so the resolution happens after the org switch. See its row below.

MOVED BY A DIFFERENT ROUTE — one site follows the declaration without the hook

file reading
console/organizations/manage/AcceptInvitationPage.tsx — after accepting an invitation The app list in hand here belongs to the organization the user is leaving. switchOrganization has just resolved, MetadataProvider drops its cache on an org change (objectui#4486) and refetches, and this line runs before any of that lands — so a declared-landing answer read here names the PREVIOUS org's app, which is worse than the launcher. The two other org-switch paths (layout/WorkspaceSwitcher.tsx, console/organizations/OrganizationsPage.tsx) full-page-navigate to the console ROOT for exactly this reason and let RootLandingRedirect resolve the landing afterwards. This page was the THIRD site of that same transition and the only one not taking its shape, so it now lands on the console ROOT too (window.location.href = resolveRootUrl()) and RootLandingRedirect resolves the declaration for the org just JOINED. Seat ruling A on the fall-back stop (objectui#7373, 2026-09-19); the cache-timing reading is written at the call site, and the source scan keys this file on resolveRootUrl rather than on the hook, so a later edit that folds it onto useHomePath() goes red there.

NOT MOVED — with the reason, per the dispatch's requirement

file why it stays
utils/homePath.tsHOME_LAUNCHER_PATH it IS the launcher, and the ?? fallback all nine moved sites resolve through. Changing it is option C, excluded by the ruling: it would strip the environment layer of its real launcher (ADR-0075)
console/ConsoleShell.tsxRootRedirect not a recovery redirect: it is /'s landing. /'s policy is resolveLandingPath, which layers a single-visible-app emptiness heuristic (objectui#4048) and refuses to conclude from an unresolved list (objectui#4233). A third reading without those is a design question — raised below, not settled here
apps/console/.../RootLandingRedirect.tsxreturn '/home' this IS rule 3 of the / policy — the documented answer when nothing is declared. Moving it is option C by another route
apps/console/src/App.tsxRoute path="/home" the route declaration. Move it and /home stops existing
apps/console/src/inbox-arrival-preview.tsx a MemoryRouter initialEntries preview fixture — a start URL, not a destination
layout/UnifiedSidebar.tsx x2, console/AppContent.tsx x1 prose comments about past defects. (The AppContent one was reworded because it quoted the line this change rewrote.)

Fixture discipline — what happened to the pins, and why each still fails for the right reason

The existing /home assertions are kept, not re-aimed. Every one of them renders with an app list that declares nothing (or with no metadata provider at all), and for that population the resolved path is /home — so they now pin the undeclared-deployment case, and they still fail if a site navigates anywhere else.

⚠️ On their own they are not enough, and that is the trap this card could have fallen into: an unchanged-and-still-green pin passes identically on the fixed and the unfixed implementation. So every moved site also gained a declared-deployment pin, which is what fails on the previous code. The one pin whose expected value genuinely changed is resolveCollapseToDockTarget's, because the function took a new required argument; its launcher case is kept as a separate assertion so both answers stay pinned.

resolveCollapseToDockTarget aside, no expectation was rewritten.

3. Evidence

Reverse verification (mutate → prove it landed on disk → run → restore; trap on absolute paths, restoration proved by an empty git diff HEAD and a blob-hash comparison, not by an exit code):

leg mutation result
A — AppContent navigate(homePath) 1 → 0, navigate('/home') 0 → 1; to={homePath} 1 → 0, to="/home" 0 → 1 3 failed, 31 passed — exactly the two new behavioural pins and the source scan. Every pre-existing /home pin stayed green, which is the point: they cannot tell the two implementations apart
B — StudioRoute both homePath reads back to literals (2 literals on disk, verified by count) 3 failed, 27 passed — the gate pin, the wordmark pin and the scan

Restored byte-identically both times (898a638de… and 363d5b8f6… match HEAD).

Suites (repo-root invocation; all at ad21d1a59):

run result
vitest run packages/app-shell/src/console/ packages/app-shell/src/views/studio-design/ 139 files, 887 tests passed
vitest run packages/app-shell/src/__tests__/ .../hooks/ .../utils/ .../chrome/ .../layout/ 121 files, 1533 tests passed
pnpm --filter @object-ui/console test 101 files, 1161 tests passed
pnpm --filter @object-ui/app-shell type-check exit 0
pnpm --filter @object-ui/console type-check exit 0
pnpm exec eslint over the 15 changed files exit 0 — 0 errors, and 0 warnings on the lines this change wrote (the fixtures are typed with MetadataContextValue, not any)
node scripts/check-changeset-presence.mjs exit 0 — 13 published source files, 1 changeset declared
node scripts/check-changeset-no-major.mjs · check-control-bytes.mjs exit 0

Each exit code was captured before any pipe, from a redirected log.

4. One question raised, carried to its own card

  1. RootRedirect (console/ConsoleShell.tsx). It is the / element published to consumers that do not mount apps/console's RootLandingRedirect, and it ignores the declaration. Folding it into useHomePath() would give / a third answer — one without the emptiness heuristic and without the unresolved-list guard its twin has. Out of this card's subject (it is not a recovery exit), so it stays, documented at the site. Seat ruling A for this card; the follow-up is filed separately as objectui#10042.

Acceptance notes

Noted while in these files, not filed and not fixed here:

  • apps/console/src/components/StudioRoute.tsx ships a user-visible Chinese title attribute on the Studio wordmark, outside i18n, and views/studio-design/StudioDesignSurface.tsx carries Chinese source comments. AGENTS.md commandment #-1 is explicit that user-facing text and comments are English-only. Untouched: out of this card's scope, and the title is a behaviour change rather than a mechanical one.
  • apps/console/src/components/StudioRoute.test.tsx's @object-ui/app-shell mock carries a comment explaining that the factory pulls one submodule "instead" of the whole barrel, while the factory does call importOriginal() on the barrel. The comment's own timing figures therefore describe a shape the code no longer has.

Generated by Claude Code


Generated by Claude Code

…tui#7373)

`app.isDefault` declares where a deployment's home is. objectui#7256 (PR
#7372) moved the console chrome's four Home affordances onto
`useHomePath()`; the "you cannot be here" exits still named the
environment launcher literally, so a control-plane customer refused an
app — or sent off a surface their runtime does not serve — landed among
"Build an app" / "Start from a template" cards that act on an
environment their deployment does not have, beside "Your apps" tiles
that are the control plane's own internal management apps.

Measured first, per the ruling on the card: every `/home` occurrence in
`packages/app-shell/src` + `apps/console/src` was classified before a
line moved, and the classification ships in the pull request body. Nine
live occurrences moved, one is held pending a decision, and the rest
must not move — including the two this change is most likely to be
misread as covering (`HOME_LAUNCHER_PATH`, and `RootRedirect`, which is
`/`'s landing and has its own resolver).

Retargeted onto the existing policy; no new policy was written:

- `AppContent` — the access-denied screen's way back, and the bounce for
  a viewer with no app to enter;
- `RequireAiSurface` — a runtime serving no AI agent. Only the DEFAULT
  moved: a host passing `redirectTo` still wins;
- `AiChatPage` — the no-agent screen's Home, and the collapse-to-dock
  landing on a cold deep link. `resolveCollapseToDockTarget` takes the
  home path as a required argument rather than naming one, so a new call
  site cannot silently reintroduce the literal;
- `StudioDesignSurface` — eviction when the package under the editor is
  deleted, and the header Home button;
- `apps/console` — the `/studio` entry gate, and the Studio front door's
  wordmark beside it (two affordances one route apart may not name two
  different homes).

`AcceptInvitationPage` is deliberately unchanged: it navigates
immediately after an organization switch, where the app list in hand
still belongs to the organization being left. The reading is recorded at
the call site and on the card.

Every ordinary environment is unchanged — where nothing is declared, and
wherever the list is not yet an answer, the resolved path is `/home`.
Each moved site gained a behavioural pin that fails on the previous
implementation, and the existing `/home` pins are kept as the
undeclared-deployment case rather than re-aimed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 4 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6169-chatbot-authoring-face-type.md

  • names packages/app-shell/src/console/ai/AiChatPage.tsxpackages/app-shell/src/console/ai/AiChatPage.tsx — edited by this change

    • showTimestamp, userAvatarUrl, userAvatarFallback, assistantAvatarUrl, assistantAvatarFallback, maxHeight — display fields. - autoResponse, autoResponseText, autoResponseDelay — the local auto-response (demo/playground) fields, already live via a real consumer (packages/app-shell/src/console/ai/AiChatPage.tsx). - onSend?: (content: string, messages: ChatMessage[]) => void — the send-callback, now typed against the published ChatMessage shape rather than the plugin's internal runtime message type.

.changeset/6515-record-form-current-user-normaliser.md

  • names console/AppContent.tsxpackages/app-shell/src/console/AppContent.tsx — edited by this change

    The normaliser moved from console/AppContent.tsx to providers/expressionUser.ts, beside the ExpressionProvider it feeds. That move is what made the fix available: RecordFormPage is lazy()-loaded BY AppContent, so importing the normaliser from its old home would have put a static edge from the split chunk back into the module it was split out of. Both console/AppContent.js and the package entry re-export the name, so buildExpressionUser is published exactly as before.

.changeset/6559-expression-user-input-contract.md

  • names console/AppContent.tsxpackages/app-shell/src/console/AppContent.tsx — edited by this change

    NO RUNTIME BEHAVIOUR MOVES. All four in-repo production call sites pass useAuth().user, typed AuthUser | null, and type cleanly unchanged — two in console/AppContent.tsx, one in views/RecordFormPage.tsx, one in apps/console's InternalFormRoute.tsx. The body is byte-equivalent: the same keys, the same ?? defaults, the same anonymous branch. ⛔ No consumer-side fallback was added; id: u.id ?? null remains the rejected shape (triage ruling 2026-08-26), because a lenient default in the consumer is what AGENTS.md #0.1 forbids and it silently equates "signed in, no id" with "signed out".

.changeset/6681-declared-lazy-marketplace-routes.md

  • names views/studio-design/StudioDesignSurface.tsxpackages/app-shell/src/views/studio-design/StudioDesignSurface.tsx — edited by this change

    Both marketplace chunks were held by CHUNK CO-TENANCY, not by any import of the page: rolldown had parked components/SuggestedBindingsPanel.tsx (statically imported by the eager views/studio-design/StudioDesignSurface.tsx) in the first, and console/marketplace/InstalledListWidget.tsx (bare-imported by the package barrel for its SDUI registration) in the second. An app-shell-eager-leaves group in apps/console/vite.config.ts isolates those co-tenants so the three declared-lazy pages chunk by their own dynamic-only reachability. The console eager closure moves 3180.2 KB → 3171.5 KB gzipped (−8,888 bytes, 48 → 45 eager chunks) with the three per-chunk ceilings unmoved.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 0febe1a59 (merge-base with origin/main): 15 file(s) changed outside .changeset/, read against 1217 pending declaration(s) that publish a body (1777 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3056.6 KB 3104.5 KB
Main entry chunk (gzip) 145.8 KB 350 KB
Entry file index-C2l5VXRc.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.90KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 50.26KB 14.36KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 255.18KB 66.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 139.56KB 35.40KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.55KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…jectui#7373)

The held site from this branch's first round, now ruled: the seat took
option A after verifying the basis on `origin/main` rather than taking
the report's word for it.

`handleAccept` ran `navigate('/home')` in-router, immediately after
`switchOrganization` resolved. That is why it could not simply read
`useHomePath()` like this card's nine other sites: the app list in
memory at that instant belongs to the organization the user is LEAVING.
`switchOrganization` only updates auth state, `MetadataProvider` drops
its whole cache when the active org changes (objectui#4486) and
refetches, and this line runs before any of that can land — so a
declared-landing answer read here would name the PREVIOUS org's app, one
the new organization may not even carry. Worse than the launcher it
would have replaced.

Landing on the console ROOT resolves the declaration AFTER the switch
instead of before it: the full page load re-seats every data scope on
the new organization, and `RootLandingRedirect` then reads that org's
list through `resolveLandingPath`. So the page honours `app.isDefault`
for the organization the user has just joined — which is what this card
asked for — by the only route that can read it.

This is not a new mechanism. `layout/WorkspaceSwitcher.tsx`
(`handleSwitch`) and `console/organizations/OrganizationsPage.tsx`
(`handleSelect`) both do `window.location.href = resolveRootUrl()` after
the same call, and both say why in a comment. Accepting an invitation is
the third site of one transition and was the only one not taking it.
`resolveRootUrl()` rather than a bare `'/'` because a full-page
navigation leaves React Router, so nothing applies the host's
`basename`.

The reading is written at the call site, not left in a thread.

The pin that asserted "lands on /home" is rewritten rather than
loosened: it now captures the full-page navigation, asks the RESOLVED
target (the way a browser resolves it, so the embeddable build's
relative `'./'` stays legal) whether it is the console root, and asserts
the launcher route — still declared in the fixture on purpose — was not
reached. The source scan gains this file keyed on `resolveRootUrl`, not
on the hook, so a later edit that "unifies" this onto `useHomePath()`
fails there instead of passing quietly.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3056.6 KB 3104.5 KB
Main entry chunk (gzip) 145.8 KB 350 KB
Entry file index-BE-t8GiH.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.90KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 50.26KB 14.36KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 255.18KB 66.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 139.56KB 35.40KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.55KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

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

Projects

None yet

2 participants