mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
fix(app): initialize reconnect policy from current app state
Initialize visibility before host bootstrap so a background mount cannot leave reconnect enabled. Seed sidebar fixtures through the workspace owner so cache hydration preserves their rows.
This commit is contained in:
@@ -69,6 +69,7 @@ import {
|
||||
resolveStartupNavigationReady,
|
||||
shouldRunStartupGiveUpTimer,
|
||||
startHostRuntimeBootstrap,
|
||||
bindHostRuntimeAppState,
|
||||
type StartupBlocker,
|
||||
} from "@/navigation/host-runtime-bootstrap";
|
||||
import { registerWorkspaceRouteNavigationRef } from "@/navigation/workspace-route-navigation";
|
||||
@@ -374,10 +375,7 @@ async function shouldStartBuiltInDaemon(): Promise<boolean> {
|
||||
function HostRuntimeBootstrapProvider({ children }: { children: ReactNode }) {
|
||||
useEffect(() => {
|
||||
const store = getHostRuntimeStore();
|
||||
const subscription = AppState.addEventListener("change", (nextState) => {
|
||||
store.setAppVisible(nextState === "active");
|
||||
});
|
||||
return () => subscription.remove();
|
||||
return bindHostRuntimeAppState(store, AppState);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
} from "@/runtime/host-runtime";
|
||||
import type { HostProfile } from "@/types/host-connection";
|
||||
import { useSessionStore, type WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import { seedSessionWorkspaces } from "@/test/seed-session";
|
||||
import { seedRuntimeWorkspaces } from "@/test/seed-session";
|
||||
import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
|
||||
import { useWorkspaceFields } from "@/stores/session-store-hooks";
|
||||
import { useActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store";
|
||||
@@ -167,7 +167,7 @@ function initializeSidebarState(workspaces: WorkspaceDescriptor[]): void {
|
||||
act(() => {
|
||||
setHostProfiles([makeHost()]);
|
||||
useSessionStore.getState().initializeSession(SERVER_ID, null as unknown as DaemonClient);
|
||||
seedSessionWorkspaces(SERVER_ID, new Map(workspaces.map((entry) => [entry.id, entry])));
|
||||
seedRuntimeWorkspaces(SERVER_ID, new Map(workspaces.map((entry) => [entry.id, entry])));
|
||||
useSessionStore.getState().setHasHydratedWorkspaces(SERVER_ID, true);
|
||||
useSidebarOrderStore.setState({
|
||||
projectOrder: ["project-a", "project-b"],
|
||||
|
||||
@@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getHostRuntimeStore } from "@/runtime/host-runtime";
|
||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||
import { useSessionStore, type WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import { seedSessionWorkspaces } from "@/test/seed-session";
|
||||
import { seedRuntimeWorkspaces } from "@/test/seed-session";
|
||||
import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
|
||||
import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
|
||||
import { useSidebarViewStore } from "@/stores/sidebar-view-store";
|
||||
@@ -96,7 +96,7 @@ describe("WorkspaceShortcutTargetsSubscriber", () => {
|
||||
act(() => {
|
||||
setHostProfiles([hostProfile()]);
|
||||
useSessionStore.getState().initializeSession("srv", null as unknown as DaemonClient);
|
||||
seedSessionWorkspaces(
|
||||
seedRuntimeWorkspaces(
|
||||
"srv",
|
||||
new Map([
|
||||
["ws-1", workspaceDescriptor({ id: "ws-1", name: "Workspace 1" })],
|
||||
@@ -142,7 +142,7 @@ describe("WorkspaceShortcutTargetsSubscriber", () => {
|
||||
it("publishes status-mode shortcut targets in visual status order", async () => {
|
||||
act(() => {
|
||||
useSidebarViewStore.getState().setGroupMode("status");
|
||||
seedSessionWorkspaces(
|
||||
seedRuntimeWorkspaces(
|
||||
"srv",
|
||||
new Map([
|
||||
[
|
||||
@@ -214,11 +214,11 @@ describe("WorkspaceShortcutTargetsSubscriber", () => {
|
||||
setHostProfiles([hostProfile("host-a"), hostProfile("host-b")]);
|
||||
useSessionStore.getState().initializeSession("host-a", null as unknown as DaemonClient);
|
||||
useSessionStore.getState().initializeSession("host-b", null as unknown as DaemonClient);
|
||||
seedSessionWorkspaces(
|
||||
seedRuntimeWorkspaces(
|
||||
"host-a",
|
||||
new Map([["a-1", workspaceDescriptor({ id: "a-1", name: "Host A" })]]),
|
||||
);
|
||||
seedSessionWorkspaces(
|
||||
seedRuntimeWorkspaces(
|
||||
"host-b",
|
||||
new Map([["b-1", workspaceDescriptor({ id: "b-1", name: "Host B" })]]),
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
resolveStartupRoute,
|
||||
shouldRunStartupGiveUpTimer,
|
||||
startHostRuntimeBootstrap,
|
||||
bindHostRuntimeAppState,
|
||||
} from "./host-runtime-bootstrap";
|
||||
import type { DaemonStartResult, StartDaemonIfEnabledInput } from "@/runtime/daemon-start-service";
|
||||
|
||||
@@ -393,3 +394,34 @@ describe("resolveHostIndexRoute", () => {
|
||||
).toEqual("/open-project");
|
||||
});
|
||||
});
|
||||
|
||||
describe("host runtime app lifecycle", () => {
|
||||
it.each(["inactive", "background"] as const)(
|
||||
"starts with reconnect disabled when mounted %s",
|
||||
(currentState) => {
|
||||
const visibility: boolean[] = [];
|
||||
let listener: ((state: "active" | "background") => void) | undefined;
|
||||
let subscribed = true;
|
||||
const dispose = bindHostRuntimeAppState(
|
||||
{ setAppVisible: (visible) => visibility.push(visible) },
|
||||
{
|
||||
currentState,
|
||||
addEventListener: (_event, handler) => {
|
||||
listener = handler;
|
||||
return {
|
||||
remove: () => {
|
||||
subscribed = false;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(visibility).toEqual([false]);
|
||||
listener?.("active");
|
||||
listener?.("background");
|
||||
expect(visibility).toEqual([false, true, false]);
|
||||
dispose();
|
||||
expect(subscribed).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AppState } from "react-native";
|
||||
import type { ActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store";
|
||||
import type {
|
||||
DaemonStartCondition,
|
||||
@@ -250,3 +251,14 @@ export function resolveStartupRoute(input: ResolveStartupRouteInput): StartupRou
|
||||
|
||||
return resolveReadyIndexStartupRoute(input);
|
||||
}
|
||||
|
||||
export function bindHostRuntimeAppState(
|
||||
store: { setAppVisible: (visible: boolean) => void },
|
||||
appState: Pick<typeof AppState, "currentState" | "addEventListener">,
|
||||
): () => void {
|
||||
const subscription = appState.addEventListener("change", (state) => {
|
||||
store.setAppVisible(state === "active");
|
||||
});
|
||||
store.setAppVisible(appState.currentState === "active");
|
||||
return () => subscription.remove();
|
||||
}
|
||||
|
||||
@@ -56,3 +56,17 @@ export function seedSessionHosts(serverIds: readonly string[]): void {
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function seedRuntimeWorkspaces(
|
||||
serverId: string,
|
||||
workspaces: Map<string, WorkspaceDescriptor>,
|
||||
): void {
|
||||
const runtime = getHostRuntimeStore();
|
||||
for (const id of useSessionStore.getState().sessions[serverId]?.workspaces.keys() ?? []) {
|
||||
if (!workspaces.has(id)) runtime.removeWorkspaceSnapshot(serverId, id);
|
||||
}
|
||||
for (const workspace of workspaces.values()) {
|
||||
runtime.acceptProjectSnapshot(serverId, projectFromWorkspace(workspace));
|
||||
}
|
||||
runtime.acceptWorkspaceSnapshots(serverId, [...workspaces.values()]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user