fix(app): keep reload splash continuous
Treat desktop settings evaluation as part of daemon startup so restored app chrome cannot render between bootstrap phases.
This commit is contained in:
@@ -164,6 +164,53 @@ describe("DaemonStartService", () => {
|
||||
expect(runningSnapshots).toEqual([true, false]);
|
||||
});
|
||||
|
||||
it("stays running while deciding whether the managed daemon should start", async () => {
|
||||
const fake = createFakeStore();
|
||||
let resolveCondition: ((value: boolean) => void) | undefined;
|
||||
let daemonStartCount = 0;
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () => {
|
||||
daemonStartCount += 1;
|
||||
return makeStatus();
|
||||
},
|
||||
});
|
||||
|
||||
const startPromise = service.startIfEnabled({
|
||||
shouldStart: () =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
resolveCondition = resolve;
|
||||
}),
|
||||
});
|
||||
|
||||
expect(service.isRunning()).toBe(true);
|
||||
expect(daemonStartCount).toBe(0);
|
||||
|
||||
resolveCondition?.(true);
|
||||
await startPromise;
|
||||
|
||||
expect(service.isRunning()).toBe(false);
|
||||
expect(daemonStartCount).toBe(1);
|
||||
});
|
||||
|
||||
it("finishes without starting the daemon when management is disabled", async () => {
|
||||
const fake = createFakeStore();
|
||||
let daemonStartCount = 0;
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () => {
|
||||
daemonStartCount += 1;
|
||||
return makeStatus();
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.startIfEnabled({ shouldStart: false });
|
||||
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(service.isRunning()).toBe(false);
|
||||
expect(daemonStartCount).toBe(0);
|
||||
});
|
||||
|
||||
it("clears the error and notifies subscribers when retry begins", async () => {
|
||||
const fake = createFakeStore();
|
||||
const startMock = vi
|
||||
@@ -188,19 +235,27 @@ describe("DaemonStartService", () => {
|
||||
expect(service.getLastError()).toBeNull();
|
||||
});
|
||||
|
||||
it("recordError surfaces an external error and notifies subscribers", () => {
|
||||
it("surfaces settings errors through the daemon startup state", async () => {
|
||||
const fake = createFakeStore();
|
||||
const service = new DaemonStartService({
|
||||
store: fake.store,
|
||||
startDesktopDaemon: async () => makeStatus(),
|
||||
});
|
||||
const notifications = vi.fn();
|
||||
service.subscribe(notifications);
|
||||
|
||||
service.recordError("settings file unreadable");
|
||||
const result = await service.startIfEnabled({
|
||||
shouldStart: async () => {
|
||||
throw new Error("settings file unreadable");
|
||||
},
|
||||
});
|
||||
|
||||
expect(service.getLastError()).toBe("settings file unreadable");
|
||||
expect(notifications).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
error: "Failed to evaluate desktop daemon settings: settings file unreadable",
|
||||
});
|
||||
expect(service.getLastError()).toBe(
|
||||
"Failed to evaluate desktop daemon settings: settings file unreadable",
|
||||
);
|
||||
expect(service.isRunning()).toBe(false);
|
||||
});
|
||||
|
||||
it("stops notifying after a subscriber unsubscribes", async () => {
|
||||
|
||||
@@ -3,6 +3,11 @@ import { connectionFromListen } from "@/types/host-connection";
|
||||
import type { HostRuntimeStore } from "@/runtime/host-runtime";
|
||||
|
||||
export type DaemonStartResult = { ok: true } | { ok: false; error: string };
|
||||
export type DaemonStartCondition = boolean | (() => boolean | Promise<boolean>);
|
||||
|
||||
export interface StartDaemonIfEnabledInput {
|
||||
shouldStart: DaemonStartCondition;
|
||||
}
|
||||
|
||||
type DaemonConnectionStore = Pick<HostRuntimeStore, "upsertConnectionFromListen">;
|
||||
|
||||
@@ -50,8 +55,27 @@ export class DaemonStartService {
|
||||
}
|
||||
|
||||
async start(): Promise<DaemonStartResult> {
|
||||
return this.startIfEnabled({ shouldStart: true });
|
||||
}
|
||||
|
||||
async startIfEnabled(input: StartDaemonIfEnabledInput): Promise<DaemonStartResult> {
|
||||
// Settings evaluation is part of startup. Publish the running state before
|
||||
// its first await so restored app chrome cannot appear between these phases.
|
||||
this.beginRequest();
|
||||
try {
|
||||
let shouldStart: boolean;
|
||||
try {
|
||||
shouldStart =
|
||||
typeof input.shouldStart === "boolean" ? input.shouldStart : await input.shouldStart();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return this.fail(`Failed to evaluate desktop daemon settings: ${message}`);
|
||||
}
|
||||
|
||||
if (!shouldStart) {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
const daemon = await this.invokeStartDesktopDaemon();
|
||||
const result = await upsertDesktopDaemonConnection(this.store, daemon);
|
||||
return result.ok ? result : this.fail(result.error);
|
||||
@@ -66,10 +90,6 @@ export class DaemonStartService {
|
||||
return this.lastError;
|
||||
}
|
||||
|
||||
recordError(message: string): void {
|
||||
this.setLastError(message);
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
return this.inFlightCount > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user