Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import semver from "semver";
export type RemoteHostStatus =
| { status: "skip" }
| { status: "loading" }
| { status: "offline"; hostName: string }
| {
status: "incompatible";
hostName: string;
Expand Down Expand Up @@ -47,7 +46,6 @@ export function useRemoteHostStatus(
)
.select(({ hosts }) => ({
name: hosts.name,
isOnline: hosts.isOnline,
})),
[collections, organizationId, filterMachineId],
);
Expand All @@ -61,40 +59,25 @@ export function useRemoteHostStatus(
const infoQuery = useQuery({
queryKey: ["remoteHostInfo", organizationId, hostId],
queryFn: () => getHostServiceClientByUrl(hostUrl).host.info.query(),
enabled: workspace != null && !isLocal && hostRow?.isOnline === true,
enabled: workspace != null && !isLocal,
staleTime: HOST_INFO_STALE_MS,
retry: false,
});

if (!workspace) return { status: "loading" };
if (isLocal) return { status: "skip" };
if (!isReady) return { status: "loading" };
// No matching v2Hosts row once the collection is ready — host was
// deregistered while the workspace record stuck around. Surface the
// offline screen so users have a recovery path instead of a blank div.
if (!hostRow) return { status: "offline", hostName: "Unknown host" };

if (!hostRow.isOnline) {
return { status: "offline", hostName: hostRow.name };
}

if (infoQuery.isPending) return { status: "loading" };

if (infoQuery.isError) {
// Cloud reports the host online but the relay round-trip failed —
// treat as offline; the most common cause is a stale `isOnline`
// flag after the host crashed without a clean disconnect.
return { status: "offline", hostName: hostRow.name };
}

const hostVersion = infoQuery.data.version;
if (!semver.satisfies(hostVersion, `>=${MIN_HOST_SERVICE_VERSION}`)) {
return {
status: "incompatible",
hostName: hostRow.name,
hostVersion,
minVersion: MIN_HOST_SERVICE_VERSION,
};
if (infoQuery.isSuccess) {
const hostVersion = infoQuery.data.version;
if (!semver.satisfies(hostVersion, `>=${MIN_HOST_SERVICE_VERSION}`)) {
return {
status: "incompatible",
hostName: hostRow?.name ?? "Unknown host",
hostVersion,
minVersion: MIN_HOST_SERVICE_VERSION,
};
}
}

return { status: "ready" };
Comment on lines +71 to 83
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Workspace renders before version check completes

Previously, infoQuery.isPending returned { status: "loading" }, which kept the layout in the blank-div state until the probe settled. That guard is gone. Now, while the version probe is in-flight, infoQuery.isSuccess is false so the hook skips the incompatible branch and falls straight through to return { status: "ready" }. The layout immediately mounts <WorkspaceProvider><Outlet /></WorkspaceProvider>, child components (terminals, branch list, file tree) start firing their own queries, and then — after one network round-trip — the layout tears all of that down and shows the incompatible screen. The PR description says the incompatible gate "stays", but it now fires with a visible flash of workspace content preceding it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/hooks/useRemoteHostStatus/useRemoteHostStatus.ts
Line: 71-83

Comment:
**Workspace renders before version check completes**

Previously, `infoQuery.isPending` returned `{ status: "loading" }`, which kept the layout in the blank-div state until the probe settled. That guard is gone. Now, while the version probe is in-flight, `infoQuery.isSuccess` is false so the hook skips the `incompatible` branch and falls straight through to `return { status: "ready" }`. The layout immediately mounts `<WorkspaceProvider><Outlet /></WorkspaceProvider>`, child components (terminals, branch list, file tree) start firing their own queries, and then — after one network round-trip — the layout tears all of that down and shows the incompatible screen. The PR description says the incompatible gate "stays", but it now fires with a visible flash of workspace content preceding it.

How can I resolve this? If you propose a fix, please make it concise.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useWorkspaceCreatesStore } from "renderer/stores/workspace-creates";
import { WorkspaceCreateErrorState } from "./components/WorkspaceCreateErrorState";
import { WorkspaceCreatingState } from "./components/WorkspaceCreatingState";
import { WorkspaceHostIncompatibleState } from "./components/WorkspaceHostIncompatibleState";
import { WorkspaceHostOfflineState } from "./components/WorkspaceHostOfflineState";
import { WorkspaceNotFoundState } from "./components/WorkspaceNotFoundState";
import { useRemoteHostStatus } from "./hooks/useRemoteHostStatus";
import { WorkspaceProvider } from "./providers/WorkspaceProvider";
Expand Down Expand Up @@ -84,9 +83,6 @@ function V2WorkspaceLayout() {
return <WorkspaceNotFoundState workspaceId={workspaceId} />;
}

if (hostStatus.status === "offline") {
return <WorkspaceHostOfflineState hostName={hostStatus.hostName} />;
}
if (hostStatus.status === "incompatible") {
return (
<WorkspaceHostIncompatibleState
Expand Down
Loading