From 48967cb26aa61c21036a8ec827f9fe393e76c3cd Mon Sep 17 00:00:00 2001 From: David McKay Date: Sun, 8 Nov 2026 19:49:10 -0800 Subject: [PATCH] Keep Install's bar short of full while items unpack, and say Give work to Bots on Welcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once every byte of Install's download had arrived, the bar filled while the last images were still being unpacked, so a full bar sat beside "1.1 GB of 1.1 GB · 1 of 5 items remaining" and read as done. With every byte in and items left, the bar is indeterminate and the line says "1.1 GB downloaded · Unpacking the last item" until the last one finishes. Welcome's line said "Hand work to Bots", which read with "Hand" as a noun. It now says "Give work to Bots in plain words." --- CHANGELOG.md | 6 ++++ app/src/routes/_authed/onboarding.tsx | 2 +- app/tests/onboarding-route.test.tsx | 12 +++++++ desktop/src/SetupProgress.test.tsx | 47 +++++++++++++++++++++++++++ desktop/src/SetupProgress.tsx | 40 ++++++++++++++++++----- 5 files changed, 98 insertions(+), 9 deletions(-) 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)