import React from 'react'; import { Routes, Route, Navigate, useLocation, useMatch, useParams } from 'react-router-dom'; import V2Layout from './components/V2Layout'; import V2FeaturePage from './components/V2FeaturePage'; import { useAuth } from '../context/AuthContext'; import { useTimeZoneCapture } from './hooks/useTimeZoneCapture'; import RegistrationInviteRequired from '../components/RegistrationInviteRequired'; import VerifyEmail from '../components/VerifyEmail'; import DiscordCallback from '../components/DiscordCallback'; import V2EmailVerificationBanner from './components/V2EmailVerificationBanner'; import ProtectedRoute from '../components/ProtectedRoute'; import '@fontsource-variable/bricolage-grotesque'; import '@fontsource/ibm-plex-sans/400.css'; import '@fontsource/ibm-plex-sans/500.css'; import '@fontsource/ibm-plex-sans/600.css'; import '@fontsource/ibm-plex-mono/500.css'; import './v2.css'; // D223 shared primitives (TASK-730): the keys and segment chat and the sidebar use. import './primitives/primitives.css'; import { applyV2Theme, getV2Theme } from './utils/theme'; // TASK-145 — inside the shell, only the surfaces the composer path needs stay // in this chunk (V2Layout, the auth gate, the feature frame). Everything else // is a route of its own: the chat first screen used to wait for the board, the // connectors catalogue, every admin page and the artifacts browser because they // were all one module graph with it. The Suspense boundary in V2App covers the // whole route table, so a chunk in flight shows the same the auth // check already shows. const V2Login = React.lazy(() => import('./components/V2Login')); // TASK-730 (D223): the shared primitives rendered alone, for frame checks. const V2PrimitivesPage = React.lazy(() => import('./primitives/V2PrimitivesPage')); const V2Register = React.lazy(() => import('./components/V2Register')); const V2OAuthComplete = React.lazy(() => import('./components/V2OAuthComplete')); const V2ForgotPassword = React.lazy(() => import('./components/V2ForgotPassword')); const V2ResetPassword = React.lazy(() => import('./components/V2ResetPassword')); const V2YourTeamPage = React.lazy(() => import('./components/V2YourTeamPage')); const V2InviteRedeem = React.lazy(() => import('./components/V2InviteRedeem')); const V2CommunityRedirect = React.lazy(() => import('./components/V2CommunityRedirect')); const V2SettingsPage = React.lazy(() => import('./components/V2SettingsPage')); const V2AgentProfile = React.lazy(() => import('./agents/V2AgentProfile')); const V2AgentSettingsPage = React.lazy(() => import('./agents/V2AgentSettingsPage')); const V2AgentMemory = React.lazy(() => import('./agents/V2AgentMemory')); const UserProfile = React.lazy(() => import('../components/UserProfile')); const V2AgentBYO = React.lazy(() => import('./components/V2AgentBYO')); const V2ConnectPage = React.lazy(() => import('./components/V2ConnectPage')); const ConnectorsPage = React.lazy(() => import('./connectors/ConnectorsPage')); const V2PodBoard = React.lazy(() => import('./components/V2PodBoard')); const V2RunTrace = React.lazy(() => import('./components/V2RunTrace')); const V2TaskRecord = React.lazy(() => import('./components/V2TaskRecord')); const V2ActivityPage = React.lazy(() => import('./components/V2ActivityPage')); const V2ArtifactsPage = React.lazy(() => import('./components/V2ArtifactsPage')); const ChatRoom = React.lazy(() => import('../components/ChatRoom')); const ApiDevPage = React.lazy(() => import('../components/ApiDevPage')); const PodContextDevPage = React.lazy(() => import('../components/PodContextDevPage')); const GlobalIntegrations = React.lazy(() => import('../components/admin/GlobalIntegrations')); const V2AdminUsers = React.lazy(() => import('./components/V2AdminUsers')); const V2AdminAnalytics = React.lazy(() => import('./components/V2AdminAnalytics')); class V2ErrorBoundary extends React.Component<{ children: React.ReactNode }, { error: Error | null }> { constructor(props: { children: React.ReactNode }) { super(props); this.state = { error: null }; } static getDerivedStateFromError(error: Error) { return { error }; } componentDidCatch(error: Error) { console.error('V2 runtime error:', error); } render() { if (this.state.error) { return (
Something went wrong
{this.state.error.message}
); } return this.props.children; } } // This is inline rather than an so the mark's existing three dots can // use the product typing rhythm. Its geometry matches the canonical minimal // mark in frontend/design-system/assets/commonly-mark.svg: one C ring, then // dots at x=25/32/39 with r=2.4 in a 64px viewBox. const V2Boot: React.FC = () => (
); const V2AuthenticatedShell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
); const V2RequireAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { isAuthenticated, loading } = useAuth(); const location = useLocation(); if (loading) { return ; } if (!isAuthenticated) { return ; } return {children}; }; // One gate owns both `/v2` and `/v2/pods/:podId`, so auto-selection changes // the URL without remounting the shell. Other deep routes stay behind // V2RequireAuth (the `*` branch below). const V2PodShellGate: React.FC = () => { const { isAuthenticated, loading } = useAuth(); const location = useLocation(); const podRoute = useMatch('/v2/pods/:podId'); if (loading) { return ; } if (!isAuthenticated) { return location.pathname === '/v2' || location.pathname === '/v2/' ? : ; } // Reject legacy pod-type paths before mounting V2Layout, which otherwise // remembers the type name as the last selected pod. if (podRoute?.params.podId && podTypeRoutes.has(podRoute.params.podId)) { return ; } return ; }; const podTypeRoutes = new Set(['chat', 'study', 'games', 'gaming', 'agent-admin', 'agent-room']); const V2UseCaseRedirect: React.FC = () => { const { useCaseId } = useParams<{ useCaseId: string }>(); return ; }; const feature = ( title: string, children: React.ReactNode, showPodsSidebar = false, showHeader = true, className = '', ) => ( {children} ); const V2LegacyChatRedirect: React.FC = () => { const { podId } = useParams<{ podId: string }>(); return ; }; const V2App: React.FC = () => { // The daily digest's 08:00 is the person's own (TASK-257 Phase 2b). useTimeZoneCapture(); React.useEffect(() => { applyV2Theme(getV2Theme()); }, []); React.useEffect(() => { try { sessionStorage.setItem('commonly.v2.active', '1'); } catch { // Ignore browsers that disallow sessionStorage. } // Claim the page canvas while v2 is mounted. App.tsx unconditionally // stamps `modern-ui` (the V1 dark gradient) on , and .v2-root only // paints its own box — so the V1 darkness showed through on load flash, // overscroll, and every gap (2026-08-24). The class must out-rank // body.modern-ui, not merely follow it in bundle order. document.body.classList.add('v2-canvas'); return () => { document.body.classList.remove('v2-canvas'); }; }, []); return (
}> }> } /> {/* Public read-only agent profile — the "meet the agent" identity card. Singular /v2/agent/... so it never collides with the authed plural /v2/agents (Your Team). */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Community access is resolved by the membership-gated pod probe. Keep this outside V2RequireAuth so an anonymous 401 can fall through to the public invite preview instead of the login wall. */} } /> {/* Pod invite redeem — handles its own auth gate (redirects to /v2/login?next=... when anonymous). Must sit OUTSIDE the V2RequireAuth wrapper so an anonymous click on the share link hits the gate cleanly instead of bouncing to / first. */} } /> {/* Board must precede the param routes: the static "board" segment outranks both pods/:podId and pods/:podType/:roomId in v6 matching, but keeping it first makes intent legible. */} , false, /* V2PodBoard owns its own header (pod name + back). */ false, 'v2-feature--board', )} /> , false, /* ChatRoom owns its own sticky AppBar with title/tabs; suppress the v2 feature header so the user does not get stacked chrome along the top of the route. */ false, )} /> } /> , false, /* V2YourTeamPage owns its own header — suppress the generic V2FeaturePage chrome to avoid stacked titles. */ false, )} /> } /> } /> , false, false)} /> , false, false)} /> } /> , false, // The Signal artboard owns this page's display-sized head. false, 'v2-feature--connectors', )} /> , false, false, 'v2-feature--artifacts')} /> , false, false)} /> , false, false)} /> , false, false, 'v2-feature--activity')} /> , false, false, 'v2-feature--settings')} /> } /> } /> )} /> , false, )} /> , false, )} /> , false, )} /> , false, )} /> , false, )} /> } /> )} />
); }; export default V2App;