import { findErrorTraceId } from "@t3tools/client-runtime/errors";import { type EnvironmentConnectionPresentation, RelayConnectionRegistration, RelayConnectionTarget,} from "@t3tools/client-runtime/connection";import { isAtomCommandInterrupted, squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime";import type { EnvironmentId } from "@t3tools/contracts";import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";import * as Option from "effect/Option";import { type ReactNode, useCallback, useEffect, useEffectEvent, useState } from "react"; import { environmentCatalog } from "~/connection/catalog";import { cn } from "~/lib/utils";import { relayEnvironmentDiscovery } from "~/state/relay";import { useRelayEnvironmentDiscovery } from "~/state/environments";import { useAtomCommand } from "~/state/use-atom-command";import { ConnectionStatusDot } from "../ConnectionStatusDot";import { ITEM_ROW_CLASSNAME, ITEM_ROW_INNER_CLASSNAME } from "../settings/itemRows";import { Checkbox } from "../ui/checkbox";import { Button } from "../ui/button";import { Skeleton } from "../ui/skeleton";import { toastManager } from "../ui/toast";import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";import { presentSavedCloudEnvironmentConnection } from "./cloudEnvironmentConnectionPresentation"; const EMPTY_DISCOVERY_REFRESH_INTERVAL_MS = 5_000; export interface SavedCloudEnvironmentConnection { readonly environmentId: EnvironmentId; readonly connection: EnvironmentConnectionPresentation;} function RemoteEnvironmentRowsSkeleton() { return ( <div className={ITEM_ROW_CLASSNAME}> <div className={ITEM_ROW_INNER_CLASSNAME}> <div className="min-w-0 flex-1 space-y-2"> <Skeleton className="h-4 w-32 rounded-full" /> <Skeleton className="h-3 w-20 rounded-full" /> </div> <Skeleton className="h-7 w-16 rounded-md" /> </div> </div> );} /** * The user's T3 Connect environments from relay discovery, each with a * Connect button. The primary environment is always excluded; already-saved * environments are hidden unless `showSavedEnvironments` renders them with * their live connection state (used by onboarding, where the full device mesh * should be visible). */export function CloudEnvironmentConnectRows({ primaryEnvironmentId, savedEnvironments, showSavedEnvironments = false, refreshWhileEmpty = false, empty = null, selection, onDiscoveryReady,}: { readonly primaryEnvironmentId: EnvironmentId | null; readonly savedEnvironments: ReadonlyArray<SavedCloudEnvironmentConnection>; readonly showSavedEnvironments?: boolean; readonly refreshWhileEmpty?: boolean; readonly empty?: ReactNode; readonly onDiscoveryReady?: () => void; readonly selection?: { readonly autoSelectedComputers?: Set<EnvironmentId>; readonly selectedIds: ReadonlySet<EnvironmentId>; readonly onChange: (environmentId: EnvironmentId, selected: boolean) => void; };}) { const environmentsState = useRelayEnvironmentDiscovery(); const registerEnvironment = useAtomCommand(environmentCatalog.register, { reportFailure: false, }); const refreshRelayEnvironments = useAtomCommand(relayEnvironmentDiscovery.refresh, { reportFailure: false, }); const refreshDiscoveryWhenIdle = useEffectEvent(async () => { if (environmentsState.refreshing || environmentsState.offline) return; await refreshRelayEnvironments(); }); const connectRelayEnvironment = useCallback( (environment: RelayClientEnvironmentRecord) => registerEnvironment( new RelayConnectionRegistration({ target: new RelayConnectionTarget({ environmentId: environment.environmentId, label: environment.label, }), }), ), [registerEnvironment], ); const [connectingEnvironmentIds, setConnectingEnvironmentIds] = useState< ReadonlySet<EnvironmentId> >(new Set()); const savedById = new Map( savedEnvironments.map((environment) => [environment.environmentId, environment]), ); useEffect(() => { let active = true; if (onDiscoveryReady || !refreshWhileEmpty || document.visibilityState === "visible") { void refreshRelayEnvironments().then(() => { if (active) onDiscoveryReady?.(); }); } return () => { active = false; }; }, [refreshRelayEnvironments, refreshWhileEmpty, onDiscoveryReady]); const connectEnvironment = async (environment: RelayClientEnvironmentRecord) => { setConnectingEnvironmentIds((current) => new Set([...current, environment.environmentId])); const result = await connectRelayEnvironment(environment); setConnectingEnvironmentIds((current) => { const next = new Set(current); next.delete(environment.environmentId); return next; }); if (result._tag === "Success") { toastManager.add({ type: "success", title: "Environment added", description: `Connecting to ${environment.label} through T3 Connect.`, }); return true; } if (isAtomCommandInterrupted(result)) { return false; } const cause = squashAtomCommandFailure(result); const message = cause instanceof Error ? cause.message : "Could not connect the T3 Connect environment."; const traceId = findErrorTraceId(cause); console.error("[t3-connect] Could not connect environment", { message, traceId, cause }); toastManager.add({ type: "error", title: "Could not connect environment", description: message, data: traceId ? { secondaryActionProps: { children: "Copy trace ID", onClick: () => void navigator.clipboard?.writeText(traceId), }, } : undefined, }); return false; }; const visibleEnvironments = [...environmentsState.environments.values()].filter( ({ environment }) => environment.environmentId !== primaryEnvironmentId && (showSavedEnvironments || !savedById.has(environment.environmentId)), ); const selectNewComputers = useEffectEvent(() => { const seen = selection?.autoSelectedComputers; if (!selection || !seen) return; for (const { environment } of visibleEnvironments) { const id = environment.environmentId; if (seen.has(id)) continue; seen.add(id); selection.onChange(id, true); if (!savedById.has(id)) { void connectEnvironment(environment).then((connected) => { if (!connected) selection.onChange(id, false); }); } } }); useEffect(() => { selectNewComputers(); }, [environmentsState.environments]); // Discovery clears its list on refresh, so poll only until a machine appears. const shouldRefreshWhileEmpty = refreshWhileEmpty && visibleEnvironments.length === 0 && !environmentsState.offline; useEffect(() => { if (!shouldRefreshWhileEmpty) return; let timer: ReturnType<typeof setTimeout> | undefined; let disposed = false; let pending = false; const visible = () => document.visibilityState === "visible"; const schedule = () => { clearTimeout(timer); if (!disposed && visible()) { timer = setTimeout(() => void refresh(), EMPTY_DISCOVERY_REFRESH_INTERVAL_MS); } }; const refresh = async () => { if (disposed || pending || !visible()) return; clearTimeout(timer); pending = true; try { await refreshDiscoveryWhenIdle(); } finally { pending = false; schedule(); } }; const onFocus = () => void refresh(); const onVisibilityChange = () => { clearTimeout(timer); if (visible()) void refresh(); }; schedule(); window.addEventListener("focus", onFocus); document.addEventListener("visibilitychange", onVisibilityChange); return () => { disposed = true; clearTimeout(timer); window.removeEventListener("focus", onFocus); document.removeEventListener("visibilitychange", onVisibilityChange); }; }, [shouldRefreshWhileEmpty]); const standalone = showSavedEnvironments || savedEnvironments.length === 0; if ( !refreshWhileEmpty && standalone && visibleEnvironments.length === 0 && environmentsState.refreshing && environmentsState.environments.size === 0 ) { return <RemoteEnvironmentRowsSkeleton />; } if (standalone && visibleEnvironments.length === 0) { // A failed or offline discovery is not "no environments" — misreporting it // as empty would read as the user's devices having disappeared. const discoveryProblem = environmentsState.offline ? "You appear to be offline." : (Option.getOrNull(environmentsState.error)?.message ?? null); if (discoveryProblem !== null && !environmentsState.refreshing) { return ( <div className={ITEM_ROW_CLASSNAME}> <p className="text-sm font-medium text-destructive"> Could not load T3 Connect environments </p> <p className="mt-1 text-xs text-muted-foreground">{discoveryProblem}</p> <Button size="sm" variant="outline" className="mt-3" onClick={() => void refreshRelayEnvironments()} > Try again </Button> </div> ); } return empty; } return visibleEnvironments.map(({ environment, availability, error }) => { const savedEnvironment = savedById.get(environment.environmentId); const savedConnection = savedEnvironment ? presentSavedCloudEnvironmentConnection(savedEnvironment.connection) : null; const dotClassName = savedConnection ? savedConnection.tone === "connected" ? "bg-success" : savedConnection.tone === "connecting" ? "bg-warning" : savedConnection.tone === "error" ? "bg-destructive" : "bg-muted-foreground/35" : availability === "online" ? "bg-success" : availability === "error" ? "bg-destructive" : availability === "checking" ? "bg-warning" : "bg-muted-foreground/35"; const statusText = savedConnection ? savedConnection.statusText : availability === "online" ? "Available · Relay online" : availability === "offline" ? "Available · Relay offline" : availability === "checking" ? "Available · Checking relay status…" : (Option.getOrNull(error)?.message ?? "Available · Relay status unavailable"); if (selection) { return ( <label key={environment.environmentId} className="flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 has-disabled:cursor-default" > <Checkbox checked={selection.selectedIds.has(environment.environmentId)} disabled={connectingEnvironmentIds.has(environment.environmentId)} onCheckedChange={async (checked) => { selection.onChange(environment.environmentId, checked); if (checked && !savedEnvironment) { const connected = await connectEnvironment(environment); if (!connected) selection.onChange(environment.environmentId, false); } }} /> <span className="min-w-0 flex-1 truncate text-sm font-medium">{environment.label}</span> <Tooltip> <TooltipTrigger render={<span tabIndex={0} />} className={cn( "max-w-[45%] shrink-0 truncate text-xs", savedConnection?.tone === "error" ? "text-destructive" : "text-muted-foreground", )} > {connectingEnvironmentIds.has(environment.environmentId) ? "Connecting…" : (savedConnection?.buttonLabel ?? (availability === "online" ? "Available" : availability === "offline" ? "Offline" : availability === "error" ? "Unavailable" : "Checking…"))} </TooltipTrigger> <TooltipPopup className="max-w-80 break-words">{statusText}</TooltipPopup> </Tooltip> </label> ); } return ( <div key={environment.environmentId} className={ITEM_ROW_CLASSNAME}> <div className={ITEM_ROW_INNER_CLASSNAME}> <div className="min-w-0"> <div className="flex items-center gap-2"> <ConnectionStatusDot dotClassName={dotClassName} pingClassName={ savedConnection?.tone === "connecting" || (savedConnection === null && availability === "checking") ? "bg-warning/60 duration-2000" : null } tooltipText={ savedConnection ? savedConnection.statusText : availability === "online" ? "Relay online" : availability === "offline" ? "Relay offline" : availability === "checking" ? "Checking relay status" : (Option.getOrNull(error)?.message ?? "Relay status unavailable") } /> <p className="truncate text-sm font-medium">{environment.label}</p> </div> <p className={cn( "mt-1 truncate text-xs", savedConnection?.tone === "error" || (savedConnection?.tone === "connecting" && savedEnvironment?.connection.error) || (savedConnection === null && availability === "error") ? "text-destructive" : "text-muted-foreground", )} > {statusText} </p> </div> {savedConnection ? ( <Button size="sm" variant="outline" disabled> {savedConnection.buttonLabel} </Button> ) : ( <Button size="sm" disabled={connectingEnvironmentIds.size > 0} onClick={() => void connectEnvironment(environment)} > {connectingEnvironmentIds.has(environment.environmentId) ? "Connecting…" : "Connect"} </Button> )} </div> </div> ); });}
import { findErrorTraceId } from "@t3tools/client-runtime/errors";
import {
type EnvironmentConnectionPresentation,
RelayConnectionRegistration,
RelayConnectionTarget,
} from "@t3tools/client-runtime/connection";
import {
isAtomCommandInterrupted,
squashAtomCommandFailure,
} from "@t3tools/client-runtime/state/runtime";
import type { EnvironmentId } from "@t3tools/contracts";
import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
import * as Option from "effect/Option";
import { type ReactNode, useCallback, useEffect, useEffectEvent, useState } from "react";
import { environmentCatalog } from "~/connection/catalog";
import { cn } from "~/lib/utils";
import { relayEnvironmentDiscovery } from "~/state/relay";
import { useRelayEnvironmentDiscovery } from "~/state/environments";
import { useAtomCommand } from "~/state/use-atom-command";
import { ConnectionStatusDot } from "../ConnectionStatusDot";
import { ITEM_ROW_CLASSNAME, ITEM_ROW_INNER_CLASSNAME } from "../settings/itemRows";
import { Checkbox } from "../ui/checkbox";
import { Button } from "../ui/button";
import { Skeleton } from "../ui/skeleton";
import { toastManager } from "../ui/toast";
import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";
import { presentSavedCloudEnvironmentConnection } from "./cloudEnvironmentConnectionPresentation";
const EMPTY_DISCOVERY_REFRESH_INTERVAL_MS = 5_000;
export interface SavedCloudEnvironmentConnection {
readonly environmentId: EnvironmentId;
readonly connection: EnvironmentConnectionPresentation;
}
function RemoteEnvironmentRowsSkeleton() {
return (
<div className={ITEM_ROW_CLASSNAME}>
<div className={ITEM_ROW_INNER_CLASSNAME}>
<div className="min-w-0 flex-1 space-y-2">
<Skeleton className="h-4 w-32 rounded-full" />
<Skeleton className="h-3 w-20 rounded-full" />
</div>
<Skeleton className="h-7 w-16 rounded-md" />
</div>
</div>
);
}
/**
* The user's T3 Connect environments from relay discovery, each with a
* Connect button. The primary environment is always excluded; already-saved
* environments are hidden unless `showSavedEnvironments` renders them with
* their live connection state (used by onboarding, where the full device mesh
* should be visible).
*/
export function CloudEnvironmentConnectRows({
primaryEnvironmentId,
savedEnvironments,
showSavedEnvironments = false,
refreshWhileEmpty = false,
empty = null,
selection,
onDiscoveryReady,
}: {
readonly primaryEnvironmentId: EnvironmentId | null;
readonly savedEnvironments: ReadonlyArray<SavedCloudEnvironmentConnection>;
readonly showSavedEnvironments?: boolean;
readonly refreshWhileEmpty?: boolean;
readonly empty?: ReactNode;
readonly onDiscoveryReady?: () => void;
readonly selection?: {
readonly autoSelectedComputers?: Set<EnvironmentId>;
readonly selectedIds: ReadonlySet<EnvironmentId>;
readonly onChange: (environmentId: EnvironmentId, selected: boolean) => void;
};
}) {
const environmentsState = useRelayEnvironmentDiscovery();
const registerEnvironment = useAtomCommand(environmentCatalog.register, {
reportFailure: false,
});
const refreshRelayEnvironments = useAtomCommand(relayEnvironmentDiscovery.refresh, {
reportFailure: false,
});
const refreshDiscoveryWhenIdle = useEffectEvent(async () => {
if (environmentsState.refreshing || environmentsState.offline) return;
await refreshRelayEnvironments();
});
const connectRelayEnvironment = useCallback(
(environment: RelayClientEnvironmentRecord) =>
registerEnvironment(
new RelayConnectionRegistration({
target: new RelayConnectionTarget({
environmentId: environment.environmentId,
label: environment.label,
}),
}),
),
[registerEnvironment],
);
const [connectingEnvironmentIds, setConnectingEnvironmentIds] = useState<
ReadonlySet<EnvironmentId>
>(new Set());
const savedById = new Map(
savedEnvironments.map((environment) => [environment.environmentId, environment]),
);
useEffect(() => {
let active = true;
if (onDiscoveryReady || !refreshWhileEmpty || document.visibilityState === "visible") {
void refreshRelayEnvironments().then(() => {
if (active) onDiscoveryReady?.();
});
}
return () => {
active = false;
};
}, [refreshRelayEnvironments, refreshWhileEmpty, onDiscoveryReady]);
const connectEnvironment = async (environment: RelayClientEnvironmentRecord) => {
setConnectingEnvironmentIds((current) => new Set([...current, environment.environmentId]));
const result = await connectRelayEnvironment(environment);
setConnectingEnvironmentIds((current) => {
const next = new Set(current);
next.delete(environment.environmentId);
return next;
});
if (result._tag === "Success") {
toastManager.add({
type: "success",
title: "Environment added",
description: `Connecting to ${environment.label} through T3 Connect.`,
});
return true;
}
if (isAtomCommandInterrupted(result)) {
return false;
}
const cause = squashAtomCommandFailure(result);
const message =
cause instanceof Error ? cause.message : "Could not connect the T3 Connect environment.";
const traceId = findErrorTraceId(cause);
console.error("[t3-connect] Could not connect environment", { message, traceId, cause });
toastManager.add({
type: "error",
title: "Could not connect environment",
description: message,
data: traceId
? {
secondaryActionProps: {
children: "Copy trace ID",
onClick: () => void navigator.clipboard?.writeText(traceId),
},
}
: undefined,
});
return false;
};
const visibleEnvironments = [...environmentsState.environments.values()].filter(
({ environment }) =>
environment.environmentId !== primaryEnvironmentId &&
(showSavedEnvironments || !savedById.has(environment.environmentId)),
);
const selectNewComputers = useEffectEvent(() => {
const seen = selection?.autoSelectedComputers;
if (!selection || !seen) return;
for (const { environment } of visibleEnvironments) {
const id = environment.environmentId;
if (seen.has(id)) continue;
seen.add(id);
selection.onChange(id, true);
if (!savedById.has(id)) {
void connectEnvironment(environment).then((connected) => {
if (!connected) selection.onChange(id, false);
});
}
}
});
useEffect(() => {
selectNewComputers();
}, [environmentsState.environments]);
// Discovery clears its list on refresh, so poll only until a machine appears.
const shouldRefreshWhileEmpty =
refreshWhileEmpty && visibleEnvironments.length === 0 && !environmentsState.offline;
useEffect(() => {
if (!shouldRefreshWhileEmpty) return;
let timer: ReturnType<typeof setTimeout> | undefined;
let disposed = false;
let pending = false;
const visible = () => document.visibilityState === "visible";
const schedule = () => {
clearTimeout(timer);
if (!disposed && visible()) {
timer = setTimeout(() => void refresh(), EMPTY_DISCOVERY_REFRESH_INTERVAL_MS);
}
};
const refresh = async () => {
if (disposed || pending || !visible()) return;
clearTimeout(timer);
pending = true;
try {
await refreshDiscoveryWhenIdle();
} finally {
pending = false;
schedule();
}
};
const onFocus = () => void refresh();
const onVisibilityChange = () => {
clearTimeout(timer);
if (visible()) void refresh();
};
schedule();
window.addEventListener("focus", onFocus);
document.addEventListener("visibilitychange", onVisibilityChange);
return () => {
disposed = true;
clearTimeout(timer);
window.removeEventListener("focus", onFocus);
document.removeEventListener("visibilitychange", onVisibilityChange);
};
}, [shouldRefreshWhileEmpty]);
const standalone = showSavedEnvironments || savedEnvironments.length === 0;
if (
!refreshWhileEmpty &&
standalone &&
visibleEnvironments.length === 0 &&
environmentsState.refreshing &&
environmentsState.environments.size === 0
) {
return <RemoteEnvironmentRowsSkeleton />;
}
if (standalone && visibleEnvironments.length === 0) {
// A failed or offline discovery is not "no environments" — misreporting it
// as empty would read as the user's devices having disappeared.
const discoveryProblem = environmentsState.offline
? "You appear to be offline."
: (Option.getOrNull(environmentsState.error)?.message ?? null);
if (discoveryProblem !== null && !environmentsState.refreshing) {
return (
<div className={ITEM_ROW_CLASSNAME}>
<p className="text-sm font-medium text-destructive">
Could not load T3 Connect environments
</p>
<p className="mt-1 text-xs text-muted-foreground">{discoveryProblem}</p>
<Button
size="sm"
variant="outline"
className="mt-3"
onClick={() => void refreshRelayEnvironments()}
>
Try again
</Button>
</div>
);
}
return empty;
}
return visibleEnvironments.map(({ environment, availability, error }) => {
const savedEnvironment = savedById.get(environment.environmentId);
const savedConnection = savedEnvironment
? presentSavedCloudEnvironmentConnection(savedEnvironment.connection)
: null;
const dotClassName = savedConnection
? savedConnection.tone === "connected"
? "bg-success"
: savedConnection.tone === "connecting"
? "bg-warning"
: savedConnection.tone === "error"
? "bg-destructive"
: "bg-muted-foreground/35"
: availability === "online"
? "bg-success"
: availability === "error"
? "bg-destructive"
: availability === "checking"
? "bg-warning"
: "bg-muted-foreground/35";
const statusText = savedConnection
? savedConnection.statusText
: availability === "online"
? "Available · Relay online"
: availability === "offline"
? "Available · Relay offline"
: availability === "checking"
? "Available · Checking relay status…"
: (Option.getOrNull(error)?.message ?? "Available · Relay status unavailable");
if (selection) {
return (
<label
key={environment.environmentId}
className="flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5 has-disabled:cursor-default"
>
<Checkbox
checked={selection.selectedIds.has(environment.environmentId)}
disabled={connectingEnvironmentIds.has(environment.environmentId)}
onCheckedChange={async (checked) => {
selection.onChange(environment.environmentId, checked);
if (checked && !savedEnvironment) {
const connected = await connectEnvironment(environment);
if (!connected) selection.onChange(environment.environmentId, false);
}
}}
/>
<span className="min-w-0 flex-1 truncate text-sm font-medium">{environment.label}</span>
<Tooltip>
<TooltipTrigger
render={<span tabIndex={0} />}
className={cn(
"max-w-[45%] shrink-0 truncate text-xs",
savedConnection?.tone === "error" ? "text-destructive" : "text-muted-foreground",
)}
>
{connectingEnvironmentIds.has(environment.environmentId)
? "Connecting…"
: (savedConnection?.buttonLabel ??
(availability === "online"
? "Available"
: availability === "offline"
? "Offline"
: availability === "error"
? "Unavailable"
: "Checking…"))}
</TooltipTrigger>
<TooltipPopup className="max-w-80 break-words">{statusText}</TooltipPopup>
</Tooltip>
</label>
);
}
return (
<div key={environment.environmentId} className={ITEM_ROW_CLASSNAME}>
<div className={ITEM_ROW_INNER_CLASSNAME}>
<div className="min-w-0">
<div className="flex items-center gap-2">
<ConnectionStatusDot
dotClassName={dotClassName}
pingClassName={
savedConnection?.tone === "connecting" ||
(savedConnection === null && availability === "checking")
? "bg-warning/60 duration-2000"
: null
}
tooltipText={
savedConnection
? savedConnection.statusText
: availability === "online"
? "Relay online"
: availability === "offline"
? "Relay offline"
: availability === "checking"
? "Checking relay status"
: (Option.getOrNull(error)?.message ?? "Relay status unavailable")
}
/>
<p className="truncate text-sm font-medium">{environment.label}</p>
</div>
<p
className={cn(
"mt-1 truncate text-xs",
savedConnection?.tone === "error" ||
(savedConnection?.tone === "connecting" && savedEnvironment?.connection.error) ||
(savedConnection === null && availability === "error")
? "text-destructive"
: "text-muted-foreground",
)}
>
{statusText}
</p>
</div>
{savedConnection ? (
<Button size="sm" variant="outline" disabled>
{savedConnection.buttonLabel}
</Button>
) : (
<Button
size="sm"
disabled={connectingEnvironmentIds.size > 0}
onClick={() => void connectEnvironment(environment)}
>
{connectingEnvironmentIds.has(environment.environmentId) ? "Connecting…" : "Connect"}
</Button>
)}
</div>
</div>
);
});
}