Skip to content

Commit b1dbe6c

Browse files
authored
fix(frontend): re-root federated Console router to avoid host __root collision (MF v2 dev) (#2508)
* 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. * fix(frontend): render DebugHelper in federated root (Cmd+Shift+D parity) The embedded MF-v2 Console renders federatedRootRoute, not __root.tsx's RootLayout, so it was missing <DebugHelper /> — the component that registers the Cmd+Shift+D debug dialog (dev-only). Render it in the federated root too, gated on NODE_ENV=development, mirroring __root.tsx. * fix(frontend): restore Toaster + content padding in federated root Mirror __root.tsx's embedded AppContent in the MF-v2 federated layout (which now renders in prod where enable-console-mf-v2 is on): - Render <Toaster> — sonner is not an MF-shared singleton, so the host's Toaster can't surface Console's toasts; without this, toast() is silent in embedded mode. - Wrap <Outlet> in the pt-8 spacing div to match AppContent. (Fullscreen-route branch is intentionally omitted — no route sets staticData.fullscreen on master; it lands with the SQL workspace.)
1 parent b816313 commit b1dbe6c

2 files changed

Lines changed: 61 additions & 7 deletions

File tree

frontend/src/federation/console-app.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,43 @@ import { protobufRegistry } from 'protobuf-registry';
3838
import { LONG_LIVED_CACHE_STALE_TIME } from 'react-query/react-query.utils';
3939

4040
import { FederatedProviders } from './federated-providers';
41+
import { federatedRootRoute } from './federated-routes';
4142
import { TokenManager } from './token-manager';
4243
import type { ConsoleAppProps } from './types';
4344
import { NotFoundPage } from '../components/misc/not-found-page';
4445
import { addBearerTokenInterceptor, checkExpiredLicenseInterceptor, config, getGrpcBasePath, setup } from '../config';
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: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ import type { QueryClient } from '@tanstack/react-query';
1414
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
1515
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router';
1616

17+
import { DebugHelper } from '../components/debug-helper/debug-dialog';
1718
import AppFooter from '../components/layout/footer';
1819
import AppPageHeader from '../components/layout/header';
1920
import { LicenseNotification } from '../components/license/license-notification';
2021
import { ErrorBoundary } from '../components/misc/error-boundary';
2122
import { ErrorDisplay } from '../components/misc/error-display';
2223
import { ErrorModalsRenderer } from '../components/misc/error-modal';
2324
import { NullFallbackBoundary } from '../components/misc/null-fallback-boundary';
25+
import { RouterSync } from '../components/misc/router-sync';
26+
import { Toaster } from '../components/redpanda-ui/components/sonner';
27+
import RequireAuth from '../components/require-auth';
2428
import { ModalContainer } from '../utils/modal-container';
2529

2630
/**
@@ -60,11 +64,22 @@ export const federatedRootRoute = createRootRouteWithContext<FederatedRouterCont
6064
*/
6165
function FederatedRootLayout() {
6266
return (
63-
<NuqsAdapter>
64-
<ErrorBoundary>
65-
<FederatedAppContent />
66-
</ErrorBoundary>
67-
</NuqsAdapter>
67+
<>
68+
<RouterSync />
69+
<NuqsAdapter>
70+
<ErrorBoundary>
71+
{/* RequireAuth triggers the user-data fetch (api.refreshUserData) that
72+
gates Console's endpoint-compatibility fetch and, in turn, the
73+
embedded sidebar items. The standalone root (__root.tsx) wraps its
74+
embedded layout the same way. */}
75+
<RequireAuth>
76+
<FederatedAppContent />
77+
</RequireAuth>
78+
</ErrorBoundary>
79+
{/* Cmd+Shift+D debug dialog — mirrors __root.tsx; dev-only. */}
80+
{process.env.NODE_ENV === 'development' && <DebugHelper />}
81+
</NuqsAdapter>
82+
</>
6883
);
6984
}
7085

@@ -73,6 +88,9 @@ function FederatedRootLayout() {
7388
* Similar to EmbeddedLayout from __root.tsx but optimized for MF v2.0.
7489
*/
7590
function FederatedAppContent() {
91+
// Mirrors __root.tsx's EmbeddedLayout so the embedded experience matches
92+
// production: AppPageHeader renders the page title (it already suppresses
93+
// the breadcrumb/sidebar-trigger in embedded mode — the host supplies those).
7694
return (
7795
<div id="mainLayout">
7896
<NullFallbackBoundary>
@@ -82,12 +100,18 @@ function FederatedAppContent() {
82100
<AppPageHeader />
83101

84102
<ErrorDisplay>
85-
<Outlet />
103+
<div className="pt-8">
104+
<Outlet />
105+
</div>
86106
</ErrorDisplay>
87107

88108
<AppFooter />
89109

90110
<ErrorModalsRenderer />
111+
112+
{/* sonner isn't an MF-shared singleton, so the host's <Toaster> can't
113+
surface Console's toasts; mirror __root.tsx's AppContent. */}
114+
<Toaster position="top-right" richColors />
91115
</div>
92116
);
93117
}

0 commit comments

Comments
 (0)