@@ -50,6 +50,8 @@ import { RegistryTab } from "./components/RegistryTab";
5050import { HostsTab } from "./components/HostsTab" ;
5151import { HostConfigCompareView } from "./components/hosts/comparison/HostConfigCompareView" ;
5252import { ConnectViewHeader } from "./components/hosts/ConnectViewHeader" ;
53+ import { ComputerView } from "./components/computer/ComputerView" ;
54+ import { useComputersEnabled } from "./hooks/useComputersEnabled" ;
5355import { motion } from "framer-motion" ;
5456import { SNAPPY_RAIL } from "./components/hosts/transition-tokens" ;
5557import OAuthDebugCallback from "./components/oauth/OAuthDebugCallback" ;
@@ -454,6 +456,8 @@ function NoRouterRouteBody({ activeTab }: { activeTab: string }) {
454456 return < HostsRoute /> ;
455457 case "host-compare" :
456458 return < HostCompareRoute /> ;
459+ case "computer" :
460+ return < ComputerRoute /> ;
457461 case "chatboxes" :
458462 return < ChatboxesRoute /> ;
459463 case "playground" :
@@ -509,11 +513,8 @@ function ActiveBillingUpsellGate() {
509513}
510514
511515export function ServersRoute ( ) {
512- const {
513- convexProjectId,
514- hostsHubFlagEnabled,
515- isAuthenticated,
516- } = useAppRouteContext ( ) ;
516+ const { convexProjectId, hostsHubFlagEnabled, isAuthenticated } =
517+ useAppRouteContext ( ) ;
517518 const navigate = useAppNavigate ( ) ;
518519
519520 // From /servers, "select a host" means navigate to /hosts/:id. State sync
@@ -523,7 +524,7 @@ export function ServersRoute() {
523524 ( next : string | null ) => {
524525 navigate ( next ? buildHostsPath ( next ) : routePaths . servers ) ;
525526 } ,
526- [ navigate ] ,
527+ [ navigate ]
527528 ) ;
528529
529530 if ( ! hostsHubFlagEnabled || ! isAuthenticated ) {
@@ -622,7 +623,7 @@ export function HostsRoute() {
622623 ( next : string | null ) => {
623624 navigate ( next ? buildHostsPath ( next ) : routePaths . hosts ) ;
624625 } ,
625- [ navigate ] ,
626+ [ navigate ]
626627 ) ;
627628
628629 if ( ! hostsHubFlagEnabled || ! isAuthenticated ) {
@@ -675,6 +676,8 @@ export function HostCompareRoute() {
675676 navigate ( routePaths . servers ) ;
676677 } else if ( next === "host" && previewedHostId ) {
677678 navigate ( buildHostsPath ( previewedHostId ) ) ;
679+ } else if ( next === "computer" ) {
680+ navigate ( routePaths . computer ) ;
678681 }
679682 } }
680683 />
@@ -683,6 +686,56 @@ export function HostCompareRoute() {
683686 ) ;
684687}
685688
689+ export function ComputerRoute ( ) {
690+ const { convexProjectId, hostsHubFlagEnabled, isAuthenticated } =
691+ useAppRouteContext ( ) ;
692+ const [ previewedHostId ] = usePreviewedHostId ( convexProjectId ) ;
693+ const navigate = useAppNavigate ( ) ;
694+ const computersEnabled = useComputersEnabled ( ) ;
695+
696+ // Flag off ⇒ the feature is hidden; bounce to Servers so a stale /computer
697+ // URL doesn't strand the user on a blank route.
698+ if ( ! computersEnabled ) {
699+ return < Navigate to = { routePaths . servers } replace /> ;
700+ }
701+
702+ const computerView = (
703+ < ComputerView
704+ projectId = { convexProjectId }
705+ isAuthenticated = { isAuthenticated }
706+ />
707+ ) ;
708+
709+ if ( ! hostsHubFlagEnabled || ! isAuthenticated ) {
710+ return computerView ;
711+ }
712+
713+ return (
714+ < motion . div
715+ key = "computer"
716+ initial = { { opacity : 0 , y : 8 } }
717+ animate = { { opacity : 1 , y : 0 } }
718+ transition = { SNAPPY_RAIL }
719+ className = "flex h-full min-h-0 flex-col"
720+ >
721+ < ConnectViewHeader
722+ value = "computer"
723+ previewedHostId = { previewedHostId }
724+ onChange = { ( next ) => {
725+ if ( next === "servers" ) {
726+ navigate ( routePaths . servers ) ;
727+ } else if ( next === "compare" ) {
728+ navigate ( routePaths . hostCompare ) ;
729+ } else if ( next === "host" && previewedHostId ) {
730+ navigate ( buildHostsPath ( previewedHostId ) ) ;
731+ }
732+ } }
733+ />
734+ < div className = "min-h-0 flex-1" > { computerView } </ div >
735+ </ motion . div >
736+ ) ;
737+ }
738+
686739export function RegistryRoute ( ) {
687740 const {
688741 registryEnabled,
@@ -714,10 +767,7 @@ export function ToolsRoute() {
714767 const prefHostStyle = usePreferencesStore ( ( state ) => state . hostStyle ) ;
715768 const hostStyle = activeHost ?. hostStyle ?? prefHostStyle ;
716769 return (
717- < ActiveHostCapsResolverScope
718- activeHost = { activeHost }
719- hostStyle = { hostStyle }
720- >
770+ < ActiveHostCapsResolverScope activeHost = { activeHost } hostStyle = { hostStyle } >
721771 < div className = "h-full overflow-hidden" >
722772 < ToolsTab
723773 serverConfig = { selectedMCPConfig }
@@ -1718,7 +1768,8 @@ export default function App() {
17181768 : currentUser . hasSeenOnboarding === true ||
17191769 currentUser . hasCompletedOnboarding === true ;
17201770 const hasSeenFirstRunOnboarding = remoteFirstRunOnboardingShown === true ;
1721- const isHostedDefaultRoute = activeTab === "servers" || activeTab === "clients" ;
1771+ const isHostedDefaultRoute =
1772+ activeTab === "servers" || activeTab === "clients" ;
17221773 const shouldHoldHostedDefaultRouteForAuth =
17231774 HOSTED_MODE &&
17241775 ! isHostedChatRoute &&
@@ -3143,75 +3194,75 @@ export default function App() {
31433194 />
31443195 < AppStateProvider appState = { effectiveAppState } >
31453196 < ServerActionsProvider
3146- actions = { {
3147- ensureServersReady,
3148- runtimeDisconnectServer : handleRuntimeDisconnect ,
3149- reconnectServer : reconnectServerForClientSwitch ,
3150- setSelectedServerNames : setSelectedMCPConfigs ,
3151- } }
3152- >
3153- < ActiveHostServerReconciler
3154- projectId = { convexProjectId }
3155- isAuthenticated = { isAuthenticated }
3156- activeHost = { activeHost }
3157- activeHostId = { activeHostId }
3158- />
3159- < AppReadyProvider
3160- isLoadingAppState = { isLoading }
3161- isConvexAuthLoading = { isAuthLoading }
3162- isConvexAuthenticated = { isAuthenticated }
3163- effectiveActiveProjectId = { activeProjectId }
3164- isLoadingRemoteProjects = { isLoadingRemoteProjects }
3197+ actions = { {
3198+ ensureServersReady,
3199+ runtimeDisconnectServer : handleRuntimeDisconnect ,
3200+ reconnectServer : reconnectServerForClientSwitch ,
3201+ setSelectedServerNames : setSelectedMCPConfigs ,
3202+ } }
31653203 >
3166- < Toaster />
3167- < MCPJamLimitDialog />
3168- < div
3169- data-testid = "app-shell"
3170- aria-hidden = { shouldShowBillingHandoffOverlay || undefined }
3171- className = {
3172- shouldShowBillingHandoffOverlay
3173- ? "pointer-events-none opacity-0"
3174- : undefined
3175- }
3176- inert = { shouldShowBillingHandoffOverlay || undefined }
3204+ < ActiveHostServerReconciler
3205+ projectId = { convexProjectId }
3206+ isAuthenticated = { isAuthenticated }
3207+ activeHost = { activeHost }
3208+ activeHostId = { activeHostId }
3209+ />
3210+ < AppReadyProvider
3211+ isLoadingAppState = { isLoading }
3212+ isConvexAuthLoading = { isAuthLoading }
3213+ isConvexAuthenticated = { isAuthenticated }
3214+ effectiveActiveProjectId = { activeProjectId }
3215+ isLoadingRemoteProjects = { isLoadingRemoteProjects }
31773216 >
3178- < HostedShellGate
3179- state = { effectiveHostedShellGateState }
3180- loadingMessage = {
3181- shouldShowPendingDashboardOAuthGate
3182- ? pendingDashboardOAuthMessage
3217+ < Toaster />
3218+ < MCPJamLimitDialog />
3219+ < div
3220+ data-testid = "app-shell"
3221+ aria-hidden = { shouldShowBillingHandoffOverlay || undefined }
3222+ className = {
3223+ shouldShowBillingHandoffOverlay
3224+ ? "pointer-events-none opacity-0"
31833225 : undefined
31843226 }
3185- onSignIn = { ( ) => {
3186- if ( chatboxPathToken ) {
3187- writeChatboxSignInReturnPath ( window . location . pathname ) ;
3227+ inert = { shouldShowBillingHandoffOverlay || undefined }
3228+ >
3229+ < HostedShellGate
3230+ state = { effectiveHostedShellGateState }
3231+ loadingMessage = {
3232+ shouldShowPendingDashboardOAuthGate
3233+ ? pendingDashboardOAuthMessage
3234+ : undefined
31883235 }
3189- signIn ( ) ;
3190- } }
3191- onSignOut = { ( ) => {
3192- void ( async ( ) => {
3193- try {
3194- await disconnectRuntimeServersForAuthExit ( ) ;
3195- } finally {
3196- await signOut ( ) ;
3236+ onSignIn = { ( ) => {
3237+ if ( chatboxPathToken ) {
3238+ writeChatboxSignInReturnPath ( window . location . pathname ) ;
31973239 }
3198- } ) ( ) ;
3199- } }
3200- >
3201- { isChatboxChatRoute ? (
3202- < ChatboxChatPage
3203- pathToken = { chatboxPathToken }
3204- onExitChatboxChat = { ( ) => setExitedChatboxChat ( true ) }
3205- />
3206- ) : (
3207- appContent
3208- ) }
3209- </ HostedShellGate >
3210- </ div >
3211- { shouldShowBillingHandoffOverlay ? (
3212- < BillingHandoffLoading overlay />
3213- ) : null }
3214- </ AppReadyProvider >
3240+ signIn ( ) ;
3241+ } }
3242+ onSignOut = { ( ) => {
3243+ void ( async ( ) => {
3244+ try {
3245+ await disconnectRuntimeServersForAuthExit ( ) ;
3246+ } finally {
3247+ await signOut ( ) ;
3248+ }
3249+ } ) ( ) ;
3250+ } }
3251+ >
3252+ { isChatboxChatRoute ? (
3253+ < ChatboxChatPage
3254+ pathToken = { chatboxPathToken }
3255+ onExitChatboxChat = { ( ) => setExitedChatboxChat ( true ) }
3256+ />
3257+ ) : (
3258+ appContent
3259+ ) }
3260+ </ HostedShellGate >
3261+ </ div >
3262+ { shouldShowBillingHandoffOverlay ? (
3263+ < BillingHandoffLoading overlay />
3264+ ) : null }
3265+ </ AppReadyProvider >
32153266 </ ServerActionsProvider >
32163267 </ AppStateProvider >
32173268 </ PreferencesStoreProvider >
0 commit comments