Skip to content

Commit 3e675dc

Browse files
committed
feat(desktop): offer leftover data as Start fresh, not a refused Start
A fresh install that found a previous installation's data refused Start and asked for a separate, two-step reset. For somebody who deleted the folder to start over, that read as an error during normal startup. The screen now offers one choice instead: Start fresh deletes exactly the data shown and carries on into Start. How to keep the data (restore the previous folder) is explained under it, and the volumes and containers it removes stay listed behind a disclosure. The failure alert is no longer shown for this case. Errors from the deletion itself are still shown as failures, and nothing starts. Starting under new names, so nothing is reused and nothing must be deleted, is tracked in FOR-345.
1 parent 7c39512 commit 3e675dc

6 files changed

Lines changed: 134 additions & 150 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
3636

3737
A desktop installation set up again after its folder was deleted reused whatever the previous one
3838
left in the container engine: Bot workspace files, and each Bot's browser profile with its website
39-
sign-ins, under the same Bot ids. Only the database was checked. Start now lists every volume and
40-
stopped container the previous installation left, and offers one confirmed reset that removes them;
41-
nothing is reused without asking. A container from it that is still running stops the reset until it
42-
is stopped. An installation with its saved key keeps its data as before.
39+
sign-ins, under the same Bot ids. Only the database was checked. Start now finds every volume and
40+
stopped container the previous installation left and offers Start fresh, which deletes exactly those
41+
and then starts; nothing is reused without asking. To keep the data, restore the previous folder
42+
instead. A container from it that is still running stops the deletion until it is stopped. An
43+
installation with its saved key keeps its data as before.
4344

4445
### A new desktop install starts its server again
4546

‎desktop/src-tauri/src/main.rs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ fn require_existing_encryption_key_with_recovery(
11341134
if found.volumes.is_empty() {
11351135
return Ok(());
11361136
}
1137-
let mut problem = Problem::plain("OpenBot found data from a previous installation, such as Bots' files and browser sign-ins. OpenBot will not reuse it in a new installation. Restore the previous installation's folder to keep it, or reset the leftover data to start fresh.");
1137+
let mut problem = Problem::plain("OpenBot found data from a previous installation, such as Bots' files and browser sign-ins. OpenBot will not reuse it in a new installation. Restore the previous installation's folder to keep it, or choose Start fresh to delete it.");
11381138
match fresh_root_without_encryption_key(root, secrets) {
11391139
Ok(true) if found.resettable() => {
11401140
problem.leftover = Some(Box::new(LeftoverReset {
@@ -1163,9 +1163,9 @@ fn require_existing_encryption_key_with_recovery(
11631163
match recovery {
11641164
Ok(Some(found)) if found.database.is_some() && found.resettable() => {
11651165
problem.said = if found.volumes.len() > 1 {
1166-
"OpenBot found a database and other data from a previous installation, but its encryption key is unavailable. Restore the original key to keep its saved data, or reset the leftover data to start fresh."
1166+
"OpenBot found a database and other data from a previous installation, but its encryption key is unavailable. Restore the original key to keep its saved data, or choose Start fresh to delete it."
11671167
} else {
1168-
"OpenBot found a database from a previous installation, but its encryption key is unavailable. Restore the original key to keep its saved data, or reset the leftover database to start fresh."
1168+
"OpenBot found a database from a previous installation, but its encryption key is unavailable. Restore the original key to keep its saved data, or choose Start fresh to delete it."
11691169
}
11701170
.into();
11711171
problem.leftover = Some(Box::new(LeftoverReset {
@@ -1271,7 +1271,9 @@ fn reset_leftover_database_with(
12711271
.as_ref()
12721272
.is_some_and(|selected| selected != root)
12731273
{
1274-
return Err(Problem::plain("The selected installation changed. Try Start again before resetting its leftover data."));
1274+
return Err(Problem::plain(
1275+
"The selected installation changed. Try Start again.",
1276+
));
12751277
}
12761278
let sorted = |names: &[String]| {
12771279
let mut names = names.to_vec();
@@ -1288,7 +1290,7 @@ fn reset_leftover_database_with(
12881290
&& offer.containers == containers
12891291
})
12901292
.cloned()
1291-
.ok_or_else(|| Problem::plain("The leftover data reset offer is no longer current. Try Start again before confirming a reset."))?;
1293+
.ok_or_else(|| Problem::plain("The leftover data changed after it was shown. Try Start again to see what is left now."))?;
12921294
let secrets = read_secrets()?;
12931295
if !fresh_root_without_encryption_key(root, &secrets)? {
12941296
return Err(Problem::plain("This installation is already configured or has its original encryption key. Its data cannot be reset here."));

‎desktop/src/App.test.tsx‎

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,7 @@ function emptyConfiguration() {
12271227
};
12281228
}
12291229

1230-
test("leftover database recovery requires confirmation and keeps setup ready to retry", async () => {
1230+
test("leftover database is offered as a fresh start that carries on into Start", async () => {
12311231
useCompatibleEndpointSetup({});
12321232
const previous = invokeHandler;
12331233
const reset = deferred<void>();
@@ -1250,21 +1250,15 @@ test("leftover database recovery requires confirmation and keeps setup ready to
12501250
await userEvent.click(view.getByRole("button", { name: "Start OpenBot" }));
12511251
const resets = () =>
12521252
invokeCalls.filter((call) => call.command === "reset_leftover_database");
1253-
await userEvent.click(
1254-
await view.findByRole("button", { name: "Reset leftover database" }),
1255-
);
1253+
const startFresh = await view.findByRole("button", { name: "Start fresh" });
1254+
// A choice, not a failure: nothing announces that Start did not finish.
1255+
expect(view.queryByRole("alert")).toBeNull();
1256+
expect(view.getByText(/saved chats, Bots, and settings/)).toBeTruthy();
12561257
expect(view.getByText(/permanently deletes/)).toBeTruthy();
1258+
expect(view.getByText(/with its original encryption key/)).toBeTruthy();
12571259
expect(view.getByText("fixture_postgres-data")).toBeTruthy();
12581260
expect(resets()).toHaveLength(0);
1259-
await userEvent.click(view.getByRole("button", { name: "Keep saved data" }));
1260-
expect(resets()).toHaveLength(0);
1261-
expect(view.queryByRole("button", { name: "Delete saved data" })).toBeNull();
1262-
await userEvent.click(
1263-
view.getByRole("button", { name: "Reset leftover database" }),
1264-
);
1265-
await userEvent.click(
1266-
view.getByRole("button", { name: "Delete saved data" }),
1267-
);
1261+
await userEvent.click(startFresh);
12681262
expect(resets()).toEqual([
12691263
{
12701264
command: "reset_leftover_database",
@@ -1276,25 +1270,22 @@ test("leftover database recovery requires confirmation and keeps setup ready to
12761270
},
12771271
},
12781272
]);
1279-
expect(
1280-
view.getByRole("button", { name: "Deleting saved data…" }),
1281-
).toHaveProperty("disabled", true);
1273+
expect(view.getByRole("button", { name: "Starting fresh…" })).toHaveProperty(
1274+
"disabled",
1275+
true,
1276+
);
12821277
expect(view.getByRole("button", { name: "Working…" })).toHaveProperty(
12831278
"disabled",
12841279
true,
12851280
);
1286-
await act(async () => reset.resolve());
1287-
expect(
1288-
view.queryByRole("button", { name: "Reset leftover database" }),
1289-
).toBeNull();
1290-
expect(view.queryByRole("alert")).toBeNull();
12911281
expect(starts).toBe(1);
1292-
await userEvent.click(view.getByRole("button", { name: "Start OpenBot" }));
1282+
await act(async () => reset.resolve());
12931283
await view.findByRole("button", { name: "Ask" });
1284+
expect(starts).toBe(2);
12941285
expect(installationCalls()).toHaveLength(1);
12951286
});
12961287

1297-
test("leftover data recovery names every container and volume and removes only that list", async () => {
1288+
test("starting fresh names every container and volume and removes only that list", async () => {
12981289
useCompatibleEndpointSetup({});
12991290
const previous = invokeHandler;
13001291
const volumes = [
@@ -1322,18 +1313,18 @@ test("leftover data recovery names every container and volume and removes only t
13221313
);
13231314
await userEvent.click(view.getByRole("button", { name: "Continue" }));
13241315
await userEvent.click(view.getByRole("button", { name: "Start OpenBot" }));
1325-
await userEvent.click(
1326-
await view.findByRole("button", { name: "Reset leftover data" }),
1327-
);
1328-
expect(view.queryByText(/local OpenBot database/)).toBeNull();
1316+
const startFresh = await view.findByRole("button", { name: "Start fresh" });
1317+
expect(view.queryByText(/saved chats/)).toBeNull();
1318+
expect(view.queryByText(/encryption key/)).toBeNull();
13291319
expect(view.getByText(/browser sign-ins/)).toBeTruthy();
1320+
expect(
1321+
view.getByText(/Restore the previous installation’s folder/),
1322+
).toBeTruthy();
13301323
for (const volume of volumes) expect(view.getByText(volume)).toBeTruthy();
13311324
expect(view.getByText(/stopped containers/)).toBeTruthy();
13321325
for (const container of containers)
13331326
expect(view.getByText(container)).toBeTruthy();
1334-
await userEvent.click(
1335-
view.getByRole("button", { name: "Delete saved data" }),
1336-
);
1327+
await userEvent.click(startFresh);
13371328
expect(
13381329
invokeCalls.filter((call) => call.command === "reset_leftover_database"),
13391330
).toEqual([
@@ -1349,7 +1340,7 @@ test("leftover data recovery names every container and volume and removes only t
13491340
]);
13501341
});
13511342

1352-
test("database recovery failure is shown without restarting services", async () => {
1343+
test("a fresh start that cannot delete the leftovers is shown without starting", async () => {
13531344
useCompatibleEndpointSetup({});
13541345
const previous = invokeHandler;
13551346
invokeHandler = async (command, args) => {
@@ -1371,12 +1362,10 @@ test("database recovery failure is shown without restarting services", async ()
13711362
await userEvent.click(view.getByRole("button", { name: "Continue" }));
13721363
await userEvent.click(view.getByRole("button", { name: "Start OpenBot" }));
13731364
await userEvent.click(
1374-
await view.findByRole("button", { name: "Reset leftover database" }),
1375-
);
1376-
await userEvent.click(
1377-
view.getByRole("button", { name: "Delete saved data" }),
1365+
await view.findByRole("button", { name: "Start fresh" }),
13781366
);
13791367
expect(await view.findByText(/database is still in use/)).toBeTruthy();
1368+
expect(view.getByRole("alert")).toBeTruthy();
13801369
expect(
13811370
invokeCalls.filter((call) => call.command === "start_stack"),
13821371
).toHaveLength(1);

‎desktop/src/App.tsx‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core";
22
import { listen } from "@tauri-apps/api/event";
33
import { useCallback, useEffect, useRef, useState } from "react";
44
import { Ask } from "./Ask";
5-
import { DatabaseReset } from "./DatabaseReset";
5+
import { StartFresh } from "./StartFresh";
66
import {
77
DEFAULT_HARNESS,
88
type HarnessChoice,
@@ -609,7 +609,8 @@ export function App() {
609609
}
610610
}
611611

612-
async function resetLeftoverData(volumes: string[], containers: string[]) {
612+
/** The leftovers go first, then Start carries on: starting fresh is one choice, not two. */
613+
async function startFresh(volumes: string[], containers: string[]) {
613614
setBusy(true);
614615
try {
615616
await invoke("reset_leftover_database", {
@@ -618,14 +619,17 @@ export function App() {
618619
containers,
619620
confirmed: true,
620621
});
621-
setFailure(null);
622-
setRecoveryFailure(null);
623-
setSteps([]);
624622
} catch (error) {
625623
setFailure(asProblem(error));
624+
return;
626625
} finally {
626+
// `start` sets `busy` itself, and its early return would never clear it.
627627
setBusy(false);
628628
}
629+
setFailure(null);
630+
setRecoveryFailure(null);
631+
setSteps([]);
632+
await start();
629633
}
630634

631635
function modelCanStart() {
@@ -991,13 +995,13 @@ export function App() {
991995
back here, and a heading that still says everything is running while the box underneath
992996
names the process that stopped is a screen arguing with itself. */}
993997
{!running && leftoverVolumes.length > 0 && (
994-
<DatabaseReset
998+
<StartFresh
995999
key={`${root}:${leftoverVolumes.join(",")}:${leftoverContainers.join(",")}`}
9961000
busy={busy}
9971001
containers={leftoverContainers}
9981002
database={Boolean(displayedFailure?.database_reset)}
9991003
volumes={leftoverVolumes}
1000-
onReset={resetLeftoverData}
1004+
onStartFresh={startFresh}
10011005
/>
10021006
)}
10031007

@@ -1270,7 +1274,10 @@ export function App() {
12701274

12711275
<SetupProgress steps={steps} />
12721276

1273-
{displayedFailure && <Failure problem={displayedFailure} />}
1277+
{/* Leftover data is a choice, offered above, not a failure to report here. */}
1278+
{displayedFailure && leftoverVolumes.length === 0 && (
1279+
<Failure problem={displayedFailure} />
1280+
)}
12741281

12751282
{!running && !returningToInstallation && (
12761283
<details>

‎desktop/src/DatabaseReset.tsx‎

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)