diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebe412ae9b..2af670c45f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,10 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
Bot has no folders it can read right now. Folder access ends when you stop OpenBot. To give it a
folder, use Choose folder in Settings → Computer." It said "No host folders are granted to this
Bot."
+- **Install's bar is full only when it is done.** Once every byte has arrived and the last items
+ are still unpacking, the bar moves without filling and the line says "1.1 GB downloaded ·
+ Unpacking the last item". It showed a full bar beside "1.1 GB of 1.1 GB · 1 of 5 items
+ remaining".
### Teach a Bot
@@ -173,6 +177,8 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
- LangChain is called **LangChain**, with LangChain's own mark, where it said "LangChain
(LangGraph)": in New Bot, a Bot's "Powered by" subtitle and its Runs on row, and OpenBot Desktop's
note under a ChatGPT plan.
+- Onboarding's Welcome says "Give work to Bots in plain words.", where it said "Hand work to
+ Bots".
## 0.3.4
diff --git a/app/src/routes/_authed/onboarding.tsx b/app/src/routes/_authed/onboarding.tsx
index 2ee7fec549..c07a5295cb 100644
--- a/app/src/routes/_authed/onboarding.tsx
+++ b/app/src/routes/_authed/onboarding.tsx
@@ -138,7 +138,7 @@ function welcomeStep(computer: boolean): OnboardingStep {
}
>
-
Hand work to Bots in plain words. They tell you when it's done.
+ Give work to Bots in plain words. They tell you when it's done.
),
demo: () => ,
diff --git a/app/tests/onboarding-route.test.tsx b/app/tests/onboarding-route.test.tsx
index adf7677201..7c98a49a01 100644
--- a/app/tests/onboarding-route.test.tsx
+++ b/app/tests/onboarding-route.test.tsx
@@ -315,6 +315,18 @@ test("arriving without a step writes the first one into the address, in place",
expect(view.history.length).toBe(1);
});
+// "Hand work to Bots" was read with "Hand" as a noun. "Give" can only be the verb.
+test("Welcome says to give work to Bots in plain words", async () => {
+ const view = open("/onboarding?step=welcome");
+ await heading(view, "Welcome to OpenBot");
+ expect(
+ view.getByText(
+ "Give work to Bots in plain words. They tell you when it's done.",
+ ),
+ ).toBeTruthy();
+ expect(view.queryByText(/Hand work to Bots/)).toBeNull();
+});
+
test("Continue adds a step to the address and to history, and saves it", async () => {
const view = open("/onboarding?step=welcome");
await heading(view, "Welcome to OpenBot");
diff --git a/desktop/src/SetupProgress.test.tsx b/desktop/src/SetupProgress.test.tsx
index 0240c9958a..93fab1833b 100644
--- a/desktop/src/SetupProgress.test.tsx
+++ b/desktop/src/SetupProgress.test.tsx
@@ -80,6 +80,53 @@ test("install progress is the bytes downloaded out of the whole download, and di
expect(row.queryByRole("progressbar")).toBeNull();
});
+// Reported on 1.0.0-rc.1: a full bar read "1.1 GB of 1.1 GB · 1 of 5 items remaining" while the
+// last image was still being unpacked, so a person took the step for done.
+test("with every byte in and an item still unpacking, the bar is not full and says it is unpacking", () => {
+ const size = 1_100_000_000;
+ const view = render(
+ ,
+ );
+ const row = within(
+ view.getByRole("listitem", { name: "Installing OpenBot" }),
+ );
+ const said = "1.1 GB downloaded · Unpacking the last item";
+ let bar = row.getByRole("progressbar", { name: "Download progress" });
+ // No value: the indeterminate bar, not a full one.
+ expect(bar.hasAttribute("value")).toBe(false);
+ expect(bar.getAttribute("aria-valuetext")).toBe(said);
+ expect(row.getByText(said)).toBeTruthy();
+ expect(row.queryByText(/remaining/)).toBeNull();
+
+ view.rerender(
+ ,
+ );
+ bar = row.getByRole("progressbar", { name: "Download progress" });
+ expect(bar.hasAttribute("value")).toBe(false);
+ expect(
+ row.getByText("1.1 GB downloaded · Unpacking the last 3 items"),
+ ).toBeTruthy();
+
+ // Bytes still arriving: the bar shows its fraction, as before.
+ view.rerender(
+ ,
+ );
+ bar = row.getByRole("progressbar", { name: "Download progress" });
+ expect(bar.getAttribute("value")).toBe("1000000000");
+ expect(
+ row.getByText("1.0 GB of 1.1 GB · 1 of 5 items remaining"),
+ ).toBeTruthy();
+});
+
test("a download whose size is not known yet says so, and its bar does not guess", () => {
const view = render(
0;
+ const known =
+ downloads.size != null && downloads.size > 0 && !unpacking(downloads);
return (
{known ? (
@@ -281,13 +286,32 @@ function DownloadProgress({ downloads }: { downloads: Downloads }) {
);
}
-/** "1.2 GB of 3.4 GB · 1 of 5 items remaining", or what is known of it. */
-export function downloadSummary({
- bytes,
- size,
- done,
- total,
-}: Downloads): string {
+/**
+ * Every byte is in, and some items are still being unpacked, imported or tagged. That brings no
+ * more bytes, so the bytes alone would say the step is done.
+ */
+function unpacking({ bytes, size, done, total }: Downloads): boolean {
+ return (
+ size != null &&
+ size > 0 &&
+ bytes >= size &&
+ total != null &&
+ (done ?? 0) < total
+ );
+}
+
+/**
+ * "1.2 GB of 3.4 GB · 1 of 5 items remaining", or what is known of it. Once every byte is in:
+ * "3.4 GB downloaded · Unpacking the last item".
+ */
+export function downloadSummary(downloads: Downloads): string {
+ const { bytes, size, done, total } = downloads;
+ if (unpacking(downloads)) {
+ const remaining = (total ?? 0) - (done ?? 0);
+ return `${formatDownloadBytes(bytes)} downloaded · Unpacking ${
+ remaining === 1 ? "the last item" : `the last ${remaining} items`
+ }`;
+ }
const parts: string[] = [];
if (size != null) {
if (size > 0)