diff --git a/frontend/src/federation/console-app.tsx b/frontend/src/federation/console-app.tsx index e509785826..3add7754f2 100644 --- a/frontend/src/federation/console-app.tsx +++ b/frontend/src/federation/console-app.tsx @@ -38,6 +38,7 @@ import { protobufRegistry } from 'protobuf-registry'; import { LONG_LIVED_CACHE_STALE_TIME } from 'react-query/react-query.utils'; import { FederatedProviders } from './federated-providers'; +import { federatedRootRoute } from './federated-routes'; import { TokenManager } from './token-manager'; import type { ConsoleAppProps } from './types'; import { NotFoundPage } from '../components/misc/not-found-page'; @@ -45,6 +46,35 @@ import { addBearerTokenInterceptor, checkExpiredLicenseInterceptor, config, getG import { routeTree } from '../routeTree.gen'; import { installUISettingsSideEffects } from '../state/ui'; +/** + * Re-root the generated route tree onto Console's federated root. + * + * In the federated dev build, the generated tree's root route (from + * `src/routes/__root.tsx`) can be substituted by Cloud UI's own `__root` route + * — both apps compile a module with the identical id `./src/routes/__root.tsx`, + * and in the shared rsbuild/MF dev runtime the host's wins. The result is that + * the embedded Console renders Cloud UI's root chrome (its react NuqsAdapter, + * Builder.io ``, and ``/KBar) instead of Console's own + * federated layout — which breaks nuqs (NUQS-404), crashes on KBar + * (`getState is not a function`, no `KBarProvider` in this subtree), and leaves + * the embedded sidebar empty. + * + * `federatedRootRoute` lives at a Console-unique module path + * (`src/federation/federated-routes.tsx`) that cannot collide with Cloud UI, so + * reattaching the generated child routes to it guarantees the embedded app + * renders Console's own root. Standalone (`app.tsx`) and the legacy embedded + * entry keep using the generated `routeTree` unchanged. + */ +function createFederatedRouteTree() { + const childRoutes = routeTree.children ? Object.values(routeTree.children) : []; + for (const child of childRoutes) { + child.options.getParentRoute = () => federatedRootRoute; + } + return federatedRootRoute._addFileChildren(childRoutes); +} + +const federatedRouteTree = createFederatedRouteTree(); + /** * Creates an interceptor that refreshes the token on 401 and retries the request. * Uses TokenManager for deduplication and abort support. @@ -255,7 +285,7 @@ function ConsoleAppInner({ }); const r = createRouter({ - routeTree, + routeTree: federatedRouteTree, history: memoryHistory, context: { basePath: '', diff --git a/frontend/src/federation/federated-routes.tsx b/frontend/src/federation/federated-routes.tsx index 8546f2a6ec..a50b8a3468 100644 --- a/frontend/src/federation/federated-routes.tsx +++ b/frontend/src/federation/federated-routes.tsx @@ -14,6 +14,7 @@ import type { QueryClient } from '@tanstack/react-query'; import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'; import { NuqsAdapter } from 'nuqs/adapters/tanstack-router'; +import { DebugHelper } from '../components/debug-helper/debug-dialog'; import AppFooter from '../components/layout/footer'; import AppPageHeader from '../components/layout/header'; import { LicenseNotification } from '../components/license/license-notification'; @@ -21,6 +22,9 @@ import { ErrorBoundary } from '../components/misc/error-boundary'; import { ErrorDisplay } from '../components/misc/error-display'; import { ErrorModalsRenderer } from '../components/misc/error-modal'; import { NullFallbackBoundary } from '../components/misc/null-fallback-boundary'; +import { RouterSync } from '../components/misc/router-sync'; +import { Toaster } from '../components/redpanda-ui/components/sonner'; +import RequireAuth from '../components/require-auth'; import { ModalContainer } from '../utils/modal-container'; /** @@ -60,11 +64,22 @@ export const federatedRootRoute = createRootRouteWithContext - - - - + <> + + + + {/* RequireAuth triggers the user-data fetch (api.refreshUserData) that + gates Console's endpoint-compatibility fetch and, in turn, the + embedded sidebar items. The standalone root (__root.tsx) wraps its + embedded layout the same way. */} + + + + + {/* Cmd+Shift+D debug dialog — mirrors __root.tsx; dev-only. */} + {process.env.NODE_ENV === 'development' && } + + ); } @@ -73,6 +88,9 @@ function FederatedRootLayout() { * Similar to EmbeddedLayout from __root.tsx but optimized for MF v2.0. */ function FederatedAppContent() { + // Mirrors __root.tsx's EmbeddedLayout so the embedded experience matches + // production: AppPageHeader renders the page title (it already suppresses + // the breadcrumb/sidebar-trigger in embedded mode — the host supplies those). return (
@@ -82,12 +100,18 @@ function FederatedAppContent() { - +
+ +
+ + {/* sonner isn't an MF-shared singleton, so the host's can't + surface Console's toasts; mirror __root.tsx's AppContent. */} +
); }