Skip to content

Commit 43f2e42

Browse files
chelojimenezclaude
andauthored
feat(computers): Computer tab — status, terminal, delete — behind a PostHog flag (PR4b) (#2573)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 328a677 commit 43f2e42

22 files changed

Lines changed: 1555 additions & 223 deletions

mcpjam-inspector/client/src/App.tsx

Lines changed: 126 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import { RegistryTab } from "./components/RegistryTab";
5050
import { HostsTab } from "./components/HostsTab";
5151
import { HostConfigCompareView } from "./components/hosts/comparison/HostConfigCompareView";
5252
import { ConnectViewHeader } from "./components/hosts/ConnectViewHeader";
53+
import { ComputerView } from "./components/computer/ComputerView";
54+
import { useComputersEnabled } from "./hooks/useComputersEnabled";
5355
import { motion } from "framer-motion";
5456
import { SNAPPY_RAIL } from "./components/hosts/transition-tokens";
5557
import 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

511515
export 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+
686739
export 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

Comments
 (0)