Skip to content

Commit c0705fc

Browse files
committed
fix(frontend): re-root federated Console router to avoid host __root collision
Embedded Console (MF v2) renders Cloud UI's root route instead of its own in dev, because both apps compile a module with the identical id ./src/routes/__root.tsx and the shared rsbuild/MF dev runtime resolves the host's. That substitutes Cloud UI's RootComponent (react NuqsAdapter, Builder.io <Content>, <CommandPalette>/KBar) as Console's embedded root, which: crashes on useKBar (no KBarProvider in this subtree) -> empty sidebar; and leaves Console's useQueryState consumers without a matching nuqs adapter context -> NUQS-404. Re-root the federated router onto Console's own federatedRootRoute (a Console-unique module path that can't collide), and give it RouterSync + RequireAuth (RequireAuth triggers the user-data fetch that gates the endpoint-compatibility fetch and the embedded sidebar items). Standalone (app.tsx/__root.tsx) and prod are unaffected.
1 parent 05acb99 commit c0705fc

2 files changed

Lines changed: 50 additions & 6 deletions

File tree

frontend/src/federation/console-app.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { createMemoryHistory, createRouter, RouterProvider } from '@tanstack/rea
3737
import { protobufRegistry } from 'protobuf-registry';
3838
import { LONG_LIVED_CACHE_STALE_TIME } from 'react-query/react-query.utils';
3939

40+
import { federatedRootRoute } from './federated-routes';
4041
import { FederatedProviders } from './federated-providers';
4142
import { TokenManager } from './token-manager';
4243
import type { ConsoleAppProps } from './types';
@@ -45,6 +46,35 @@ import { addBearerTokenInterceptor, checkExpiredLicenseInterceptor, config, getG
4546
import { routeTree } from '../routeTree.gen';
4647
import { installUISettingsSideEffects } from '../state/ui';
4748

49+
/**
50+
* Re-root the generated route tree onto Console's federated root.
51+
*
52+
* In the federated dev build, the generated tree's root route (from
53+
* `src/routes/__root.tsx`) can be substituted by Cloud UI's own `__root` route
54+
* — both apps compile a module with the identical id `./src/routes/__root.tsx`,
55+
* and in the shared rsbuild/MF dev runtime the host's wins. The result is that
56+
* the embedded Console renders Cloud UI's root chrome (its react NuqsAdapter,
57+
* Builder.io `<Content>`, and `<CommandPalette>`/KBar) instead of Console's own
58+
* federated layout — which breaks nuqs (NUQS-404), crashes on KBar
59+
* (`getState is not a function`, no `KBarProvider` in this subtree), and leaves
60+
* the embedded sidebar empty.
61+
*
62+
* `federatedRootRoute` lives at a Console-unique module path
63+
* (`src/federation/federated-routes.tsx`) that cannot collide with Cloud UI, so
64+
* reattaching the generated child routes to it guarantees the embedded app
65+
* renders Console's own root. Standalone (`app.tsx`) and the legacy embedded
66+
* entry keep using the generated `routeTree` unchanged.
67+
*/
68+
function createFederatedRouteTree() {
69+
const childRoutes = routeTree.children ? Object.values(routeTree.children) : [];
70+
for (const child of childRoutes) {
71+
child.options.getParentRoute = () => federatedRootRoute;
72+
}
73+
return federatedRootRoute._addFileChildren(childRoutes);
74+
}
75+
76+
const federatedRouteTree = createFederatedRouteTree();
77+
4878
/**
4979
* Creates an interceptor that refreshes the token on 401 and retries the request.
5080
* Uses TokenManager for deduplication and abort support.
@@ -255,7 +285,7 @@ function ConsoleAppInner({
255285
});
256286

257287
const r = createRouter({
258-
routeTree,
288+
routeTree: federatedRouteTree,
259289
history: memoryHistory,
260290
context: {
261291
basePath: '',

frontend/src/federation/federated-routes.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { ErrorBoundary } from '../components/misc/error-boundary';
2121
import { ErrorDisplay } from '../components/misc/error-display';
2222
import { ErrorModalsRenderer } from '../components/misc/error-modal';
2323
import { NullFallbackBoundary } from '../components/misc/null-fallback-boundary';
24+
import { RouterSync } from '../components/misc/router-sync';
25+
import RequireAuth from '../components/require-auth';
2426
import { ModalContainer } from '../utils/modal-container';
2527

2628
/**
@@ -60,11 +62,20 @@ export const federatedRootRoute = createRootRouteWithContext<FederatedRouterCont
6062
*/
6163
function FederatedRootLayout() {
6264
return (
63-
<NuqsAdapter>
64-
<ErrorBoundary>
65-
<FederatedAppContent />
66-
</ErrorBoundary>
67-
</NuqsAdapter>
65+
<>
66+
<RouterSync />
67+
<NuqsAdapter>
68+
<ErrorBoundary>
69+
{/* RequireAuth triggers the user-data fetch (api.refreshUserData) that
70+
gates Console's endpoint-compatibility fetch and, in turn, the
71+
embedded sidebar items. The standalone root (__root.tsx) wraps its
72+
embedded layout the same way. */}
73+
<RequireAuth>
74+
<FederatedAppContent />
75+
</RequireAuth>
76+
</ErrorBoundary>
77+
</NuqsAdapter>
78+
</>
6879
);
6980
}
7081

@@ -73,6 +84,9 @@ function FederatedRootLayout() {
7384
* Similar to EmbeddedLayout from __root.tsx but optimized for MF v2.0.
7485
*/
7586
function FederatedAppContent() {
87+
// Mirrors __root.tsx's EmbeddedLayout so the embedded experience matches
88+
// production: AppPageHeader renders the page title (it already suppresses
89+
// the breadcrumb/sidebar-trigger in embedded mode — the host supplies those).
7690
return (
7791
<div id="mainLayout">
7892
<NullFallbackBoundary>

0 commit comments

Comments
 (0)