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
28 changes: 28 additions & 0 deletions app/src/renderer/src/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,34 @@ describe("submitCreate (session orchestration)", () => {
expect(assistant.items.at(-1)).toMatchObject({ type: "status", icon: "error", label: "no clips" });
});

it("a rejecting session:save (e.g. stale main without the handler) degrades to a log line", async () => {
vi.mocked(window.api.generateProject).mockResolvedValue({ ok: true });
vi.mocked(window.api.saveSession).mockRejectedValue(
new Error("No handler registered for 'session:save'"),
);
useEditor.setState({
edl,
slug: "demo",
view: "editor",
session: { version: 1, turns: [] },
sessionBusy: false,
notices: [],
reloadProject: () => {},
});

await useEditor.getState().submitCreate({ text: "go", settings });
// Let the fire-and-forget persistence promises settle.
await new Promise((r) => setTimeout(r, 0));

// The run itself completed, nothing toasted, and the failure was logged.
expect(useEditor.getState().session?.turns).toHaveLength(2);
expect(useEditor.getState().notices).toHaveLength(0);
expect(window.api.logRenderer).toHaveBeenCalledWith(
"warn",
expect.stringContaining("session save failed"),
);
});

it("applyCritiqueFixes marks the card applied and logs the autotune turn", async () => {
vi.mocked(window.api.autoTune).mockResolvedValue({ ok: true });
vi.mocked(window.api.saveSession).mockResolvedValue({ ok: true });
Expand Down
31 changes: 25 additions & 6 deletions app/src/renderer/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,25 @@ export interface ExportResult {
error?: string;
}

/**
* Best-effort session persistence. The in-memory log is authoritative while a
* run streams; a failed write (disk error, or a stale main process without the
* session:save handler after an update) must degrade to a log line — not an
* unhandled rejection that toasts "Something failed" at the user mid-run.
*/
function persistSession(slug: string, session: Session): void {
void window.api
?.saveSession(slug, session)
.then((res) => {
if (res && res.ok === false) {
void window.api?.logRenderer("warn", `session save failed: ${res.error ?? "unknown error"}`);
}
})
.catch((err) => {
void window.api?.logRenderer("warn", `session save failed: ${String(err)}`);
});
}

interface EditorState {
view: View;
projects: ProjectSummary[];
Expand Down Expand Up @@ -470,7 +489,7 @@ export const useEditor = create<EditorState>()((set, get) => ({
});
session = appendAssistantTurn(session, mode);
set({ session, sessionBusy: true, ...(mode === "generation" ? { generating: true } : {}) });
void window.api.saveSession(slug, session);
persistSession(slug, session);

const prefix = mode === "generation" ? "generate" : "critique";
const offPhase = window.api.onPhase(prefix, (phase) => {
Expand Down Expand Up @@ -534,12 +553,12 @@ export const useEditor = create<EditorState>()((set, get) => ({
s = completeAssistantTurn(s, res.error ?? "The run failed.");
}
set({ session: s });
void window.api.saveSession(slug, s);
persistSession(slug, s);
} catch (err) {
if (get().slug === slug) {
const s = completeAssistantTurn(get().session ?? emptySession(), String(err));
set({ session: s });
void window.api.saveSession(slug, s);
persistSession(slug, s);
}
} finally {
offPhase();
Expand All @@ -560,7 +579,7 @@ export const useEditor = create<EditorState>()((set, get) => ({
label: "applying the suggested fixes",
});
set({ session, sessionBusy: true, autotuning: true });
void window.api.saveSession(slug, session);
persistSession(slug, session);

const offPhase = window.api.onPhase("autotune", (phase) => {
if (get().slug !== slug) return;
Expand Down Expand Up @@ -597,12 +616,12 @@ export const useEditor = create<EditorState>()((set, get) => ({
s = completeAssistantTurn(s, res.error ?? "Couldn't apply the fixes.");
}
set({ session: s });
void window.api.saveSession(slug, s);
persistSession(slug, s);
} catch (err) {
if (get().slug === slug) {
const s = completeAssistantTurn(get().session ?? emptySession(), String(err));
set({ session: s });
void window.api.saveSession(slug, s);
persistSession(slug, s);
}
} finally {
offPhase();
Expand Down
Loading