diff --git a/apps/api/src/onboarding-intake.ts b/apps/api/src/onboarding-intake.ts index a5ea131..8a5828a 100644 --- a/apps/api/src/onboarding-intake.ts +++ b/apps/api/src/onboarding-intake.ts @@ -52,7 +52,6 @@ export interface FocusOption { id: string; letter: string; label: string; - summary: string; apps: string[]; /** Stored as a `goals` fact, so it stays a plain English statement. */ fact: string; @@ -63,7 +62,6 @@ export const FOCUS_OPTIONS: FocusOption[] = [ id: "day", letter: "A", label: "Day-to-day work", - summary: "Slack, calendar, and email", apps: ["slack", "gmail", "googlecalendar"], fact: "Wants help with day-to-day work first", }, @@ -71,7 +69,6 @@ export const FOCUS_OPTIONS: FocusOption[] = [ id: "inbox", letter: "B", label: "Inbox & email", - summary: "email and calendar", apps: ["gmail", "googlecalendar", "slack"], fact: "Wants help with inbox and email first", }, @@ -79,7 +76,6 @@ export const FOCUS_OPTIONS: FocusOption[] = [ id: "research", letter: "C", label: "Research & writing", - summary: "the web, notes, and docs", apps: ["hackernews", "notion", "googledocs"], fact: "Wants help with research and writing first", }, @@ -87,7 +83,6 @@ export const FOCUS_OPTIONS: FocusOption[] = [ id: "crew", letter: "D", label: "Specialist crew", - summary: "a coordinated team of focused Oos", apps: ["slack", "gmail", "googlecalendar"], fact: "Wants a coordinated crew of specialist bots", }, diff --git a/apps/mobile/lib/api.test.ts b/apps/mobile/lib/api.test.ts index ddc8e93..053a60c 100644 --- a/apps/mobile/lib/api.test.ts +++ b/apps/mobile/lib/api.test.ts @@ -1857,6 +1857,25 @@ describe("mobile thread event reduction", () => { ).toBe("Slack · Alex: Hello from the group"); }); + it("reads a choice card as its question and answer", () => { + const card = { + kind: "choice" as const, + step: "goal" as const, + question: "What’s one thing you want to get done in the next few weeks?", + textInput: { placeholder: "Your goal" }, + options: [{ id: "skip", letter: "", label: "Skip" }], + }; + expect(blockText(mobileMessage("choice-1", [card]))).toBe(card.question); + expect( + blockText( + mobileMessage("choice-2", [{ ...card, answerId: "_text", answerText: "Run a 10K" }]), + ), + ).toBe(`${card.question}\nRun a 10K`); + expect(blockText(mobileMessage("choice-3", [{ ...card, answerId: "skip" }]))).toBe( + `${card.question}\nSkip`, + ); + }); + it("deduplicates durable messages and replaces matching transient subagent state", () => { const initial = snapshot([ mobileMessage("message-1", [{ kind: "text", text: "old" }]), diff --git a/apps/mobile/lib/api.ts b/apps/mobile/lib/api.ts index f328abc..45e5777 100644 --- a/apps/mobile/lib/api.ts +++ b/apps/mobile/lib/api.ts @@ -874,6 +874,12 @@ export function blockText(message: MobileMessage) { if (block.kind === "child_bot") { return `${block.status === "archived" ? "Archived" : block.status === "deleted" ? "Deleted" : "Bot"} ${block.name ?? ""}`; } + // Choice cards are answered on the web; here they read as the question and its answer. + if (block.kind === "choice") { + const answer = + block.answerText ?? block.options.find((option) => option.id === block.answerId)?.label; + return answer ? `${block.question}\n${answer}` : block.question; + } if (block.kind === "chart") return `[chart: ${block.name ?? "chart"}]`; if (block.kind === "image") return `[image: ${block.name ?? "attachment"}]`; if (block.kind === "file") { diff --git a/apps/web/e2e/helpers.ts b/apps/web/e2e/helpers.ts index 84d7794..c5241fe 100644 --- a/apps/web/e2e/helpers.ts +++ b/apps/web/e2e/helpers.ts @@ -88,8 +88,9 @@ async function visible(locator: Locator, timeout: number) { } /** - * Walks first-run onboarding, which lands in the main conversation. Unless `mainConversation` - * is set, it then opens the main bot's own thread, where most specs start. + * Walks the first-run wizard, which lands in the main conversation with the main bot's first + * onboarding question pending (the composer works regardless). Unless `mainConversation` is + * set, it then opens the main bot's own thread, where most specs start. */ export async function completeOnboarding( page: Page, @@ -101,13 +102,12 @@ export async function completeOnboarding( if (page.url().includes("/onboarding")) { await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute( "data-onboarding-step", - /^(emoji|preferences|language|voice|integrations|scan|bot)$/, + /^(emoji|language|voice|integrations|scan|bot)$/, { timeout: 30_000 }, ); } const design = page.getByRole("heading", { name: /Design your Oo/i }); - const preferences = page.getByRole("heading", { name: /Tell us about yourself/i }); const language = page.getByRole("heading", { name: /Choose your language/i }); const voice = page.getByRole("heading", { name: /Pick a voice/i }); const integrations = page.getByRole("heading", { name: "Server integrations", exact: true }); @@ -119,8 +119,6 @@ export async function completeOnboarding( await expect(entry.first()).toBeVisible({ timeout: 20_000 }); if (await design.isVisible()) { - await page.getByRole("button", { name: "Continue", exact: true }).click(); - await expect(preferences).toBeVisible(); await page.getByRole("button", { name: "Continue", exact: true }).click(); await expect(language).toBeVisible(); await page.getByRole("button", { name: /Skip/i }).click(); @@ -133,8 +131,15 @@ export async function completeOnboarding( await page.getByRole("button", { name: "Skip", exact: true }).click(); } - // Scan step auto-advances to the main bot, then onboarding lands in the main conversation. - await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); + // The scan step, when shown, either asks first (skip it) or advances by itself. Onboarding + // then lands in the main conversation. + const skipScan = page + .locator('[data-onboarding-step="scan"]') + .getByRole("button", { name: "Skip", exact: true }); + await expect(async () => { + if (await skipScan.isVisible()) await skipScan.click(); + expect(page.url()).toMatch(/\/app\/g\/[^/]+$/); + }).toPass({ timeout: 30_000 }); await expect(chief).toBeVisible({ timeout: 15_000 }); await waitForChatComposer(page); diff --git a/apps/web/e2e/message-hover-actions.spec.ts b/apps/web/e2e/message-hover-actions.spec.ts index 4667825..d35ade3 100644 --- a/apps/web/e2e/message-hover-actions.spec.ts +++ b/apps/web/e2e/message-hover-actions.spec.ts @@ -4,7 +4,8 @@ import { captureScreenshot, completeOnboarding, signup, waitForChatComposer } fr /** Submit from the Send control so an in-flight prior send cannot swallow Enter. */ async function sendComposerMessage(page: Page, composer: Locator, text: string) { await composer.fill(text); - const send = page.getByRole("button", { name: "Send", exact: true }); + // An open onboarding card in the transcript has its own Send. + const send = page.getByTestId("composer-bar").getByRole("button", { name: "Send", exact: true }); await expect(send).toBeEnabled(); await send.click(); await expect(composer).toHaveValue(""); @@ -72,13 +73,20 @@ test("message hover shows beside-bubble actions; reply links to parent", async ( const transcript = page.getByTestId("transcript"); - // Empty start: no Fresh-start greeting. Answer the focus card so a plain - // bot text bubble exists for hover layout checks. + // Empty start: no Fresh-start greeting. Answer the focus card, then ask something long: a bot + // row's rail sits at the row's end, so the measured reply must wrap and fill its row. await expect(page.getByText("What do you want me on first?", { exact: true })).toBeVisible({ timeout: 20_000, }); await page.getByTestId("focus-choice-day").click(); - const botText = page.getByText(/Got it\./); + await expect(page.getByText(/Got it\./)).toBeVisible({ timeout: 20_000 }); + const composer = page.getByRole("combobox", { name: /^Message/ }); + await sendComposerMessage( + page, + composer, + `hover-bot-${stamp} ${"please look into this ".repeat(7)}`, + ); + const botText = page.getByText(/i handled: hover-bot-/); await expect(botText).toBeVisible({ timeout: 20_000 }); const botRow = transcript.locator(`[data-message-id]`).filter({ has: botText }).first(); await expect(botRow).toBeVisible(); @@ -121,7 +129,6 @@ test("message hover shows beside-bubble actions; reply links to parent", async ( const parentText = `hover-parent-${stamp}`; const replyText = `hover-reply-${stamp}`; - const composer = page.getByRole("combobox", { name: /^Message/ }); await expect(composer).toBeVisible(); await sendComposerMessage(page, composer, parentText); diff --git a/apps/web/e2e/onboarding-conversation.spec.ts b/apps/web/e2e/onboarding-conversation.spec.ts index 57bd3bb..e028c66 100644 --- a/apps/web/e2e/onboarding-conversation.spec.ts +++ b/apps/web/e2e/onboarding-conversation.spec.ts @@ -21,6 +21,9 @@ test("focus choice suggests apps and preserves a completed connection", async ({ await expect(page.getByTestId("focus-choice-day")).toContainText("Day-to-day work"); await expect(page.getByTestId("focus-choice-research")).toContainText("Research & writing"); await expect(page.getByTestId("focus-choice-crew")).toContainText("Specialist crew"); + // Onboarding questions offer a quiet way out instead of a dismiss button. + await expect(page.getByTestId("focus-choice-skip")).toBeVisible(); + await expect(page.getByRole("button", { name: "Dismiss" })).toHaveCount(0); await page.mouse.move(1, 1); await captureScreenshot(page, testInfo, "01-focus-choice"); await captureScreenshot(page, testInfo, "choice-card-onboarding"); @@ -33,6 +36,9 @@ test("focus choice suggests apps and preserves a completed connection", async ({ ).toBeVisible(); expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Chief"); await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible(); + await expect( + page.getByText("Got it. Slack, Gmail, and Google Calendar are a good place to start."), + ).toBeVisible(); await expect(page.getByText("Slack", { exact: true })).toBeVisible(); await expect(page.getByText("Gmail", { exact: true })).toBeVisible(); const connectionCards = page.getByRole("group", { name: / connection$/ }); @@ -45,10 +51,10 @@ test("focus choice suggests apps and preserves a completed connection", async ({ ); expect(cardBoxes[1].top - cardBoxes[0].bottom).toBeGreaterThanOrEqual(8); expect(cardBoxes[2].top - cardBoxes[1].bottom).toBeGreaterThanOrEqual(8); - await page - .getByTestId("transcript") - .getByText("Hit those three and I’ll start pulling the picture.") - .scrollIntoViewIfNeeded(); + // The next onboarding question follows the suggestions. + const goal = page.locator('[data-testid="choice-card"][data-step="goal"]'); + await expect(goal).toContainText("What’s one thing you want to get done in the next few weeks?"); + await slackCard(page).scrollIntoViewIfNeeded(); await page.mouse.move(1, 1); await captureScreenshot(page, testInfo, "02-app-suggestions"); const authorizeButton = slackCard(page).getByRole("button", { name: "Authorize" }); diff --git a/apps/web/e2e/onboarding-emoji-first.spec.ts b/apps/web/e2e/onboarding-emoji-first.spec.ts index 0a49936..51e1bf8 100644 --- a/apps/web/e2e/onboarding-emoji-first.spec.ts +++ b/apps/web/e2e/onboarding-emoji-first.spec.ts @@ -17,12 +17,11 @@ test.beforeEach(async ({ page }) => { }); /** - * E2E test for the new emoji-first onboarding flow: + * E2E test for the emoji-first onboarding flow: * 1. Sign up → lands on onboarding * 2. Design Oo: pick shape + color → live preview - * 3. Tell us about yourself: name, timezone, hobbies, work style, communication - * 4. Choose language - * 5. Skip integrations → bot created → land in chat + * 3. Choose language, skip the voice + * 4. Skip integrations → bot created → land in chat, where the main bot asks the rest */ test("emoji-first onboarding: shape + color selection with live preview", async ({ @@ -50,50 +49,13 @@ test("emoji-first onboarding: shape + color selection with live preview", async if (testInfo) await captureScreenshot(page, testInfo, "02-shape-color-preview"); - // Click Continue → goes to preferences step + // Click Continue → goes to the language step await page .getByRole("button", { name: /Continue/i }) .first() .click(); - // Step 2: Tell us about yourself - await expect(page.getByRole("heading", { name: /Tell us about yourself/i })).toBeVisible(); - - if (testInfo) await captureScreenshot(page, testInfo, "03-user-preferences"); - - // Fill in name - await page.getByPlaceholder(/Your name/i).fill("Alice"); - - // Fill in timezone - await page.getByLabel(/Timezone/i).fill("Europe/Vienna"); - - // Select a hobby - const hobbyButtons = page.locator("button[aria-pressed]"); - if (await hobbyButtons.count()) { - await hobbyButtons.first().click(); // toggle first hobby - } - - // Select work style - const workStyleButtons = page.locator("button[aria-pressed]:visible"); - if ((await workStyleButtons.count()) >= 2) { - await workStyleButtons.nth(0).click(); - } - - // Select communication style - const commButtons = page.locator("button[aria-pressed]:visible"); - if ((await commButtons.count()) >= 2) { - await commButtons.nth(0).click(); - } - - if (testInfo) await captureScreenshot(page, testInfo, "04-preferences-filled"); - - // Click Continue → goes to language step - await page - .getByRole("button", { name: /Continue/i }) - .first() - .click(); - - // Step 3: Language selection + // Step 2: Language selection await expect(page.getByRole("heading", { name: /Choose your language/i })).toBeVisible(); if (testInfo) await captureScreenshot(page, testInfo, "05-language-step"); @@ -135,13 +97,6 @@ test("onboarding: skip language defaults to English", async ({ page }) => { .first() .click(); - // Preferences step — just continue - await expect(page.getByRole("heading", { name: /Tell us about yourself/i })).toBeVisible(); - await page - .getByRole("button", { name: /Continue/i }) - .first() - .click(); - // Language step — click Skip, then skip voice. await expect(page.getByRole("heading", { name: /Choose your language/i })).toBeVisible(); await page.getByRole("button", { name: /Skip/i }).click(); diff --git a/apps/web/e2e/onboarding-intake.spec.ts b/apps/web/e2e/onboarding-intake.spec.ts new file mode 100644 index 0000000..3b7903a --- /dev/null +++ b/apps/web/e2e/onboarding-intake.spec.ts @@ -0,0 +1,83 @@ +/** + * E2E: after the wizard the main bot gets to know the user in the main conversation, one card + * after each answer, without ever blocking the composer. + */ +import { expect, type Page, test } from "@playwright/test"; +import { captureScreenshot, completeOnboarding, rpc, signup } from "./helpers"; + +function card(page: Page, step: string) { + return page.locator(`[data-testid="choice-card"][data-step="${step}"]`); +} + +test("the main bot asks focus, goal, check-ins and quiet hours, then says what is next", async ({ + page, +}, testInfo) => { + await signup(page, `intake-${Date.now()}@rakazo.test`, "password12", "Intake Tester"); + await completeOnboarding(page, undefined, { mainConversation: true }); + + await page.getByTestId("focus-choice-day").click(); + const goal = card(page, "goal"); + await expect(goal).toContainText("What’s one thing you want to get done in the next few weeks?"); + + // Typing instead of answering goes to the main bot, and the card stays answerable. + const composer = page.getByRole("combobox", { name: "Message" }); + await composer.fill("Quick question first"); + await composer.press("Enter"); + await expect(page.getByTestId("transcript")).toContainText(/on it|handled/i, { + timeout: 60_000, + }); + const goalInput = goal.getByRole("textbox", { + name: "What’s one thing you want to get done in the next few weeks?", + }); + await goalInput.fill("Run a 10K"); + await goal.scrollIntoViewIfNeeded(); + await page.mouse.move(1, 1); + await captureScreenshot(page, testInfo, "onboarding-intake-goal"); + await goalInput.press("Enter"); + await expect(goal.getByTestId("choice-answer")).toHaveText("Run a 10K"); + + const rhythm = card(page, "rhythm"); + await expect(rhythm).toContainText("When should I check in with you?"); + await rhythm.scrollIntoViewIfNeeded(); + await page.mouse.move(1, 1); + await captureScreenshot(page, testInfo, "onboarding-intake-rhythm"); + await rhythm.getByTestId("rhythm-choice-morning").click(); + await expect(rhythm.getByTestId("choice-answer")).toContainText("Every morning"); + + const quiet = card(page, "quiet_hours"); + await expect(quiet).toContainText("When should I stay quiet?"); + await quiet.getByTestId("quiet_hours-choice-22-07").click(); + + // Only a browser that can still turn push on is asked about notifications. + const closing = page.getByText(/^All set\. I’ll check in (today|tomorrow) at 8:00\sAM\.$/); + const notifications = card(page, "notifications"); + await expect(notifications.or(closing).first()).toBeVisible(); + if (await notifications.isVisible()) { + await notifications.getByTestId("notifications-choice-later").click(); + } + await expect(closing).toBeVisible(); + await expect( + page.locator('[data-testid="choice-card"]:not(:has([data-testid="choice-answer"]))'), + ).toHaveCount(0); + await closing.scrollIntoViewIfNeeded(); + await page.mouse.move(1, 1); + await captureScreenshot(page, testInfo, "onboarding-intake-done"); + + const settings = await rpc<{ quietHours: { start: string; end: string } | null }>( + page, + "notifications/settings/get", + null, + ); + expect(settings.quietHours).toEqual({ start: "22:00", end: "07:00" }); + const profile = await rpc<{ facts: Array<{ value: string; source: string }> }>( + page, + "profile/get", + null, + ); + expect(profile.facts).toContainEqual( + expect.objectContaining({ + value: "Wants help with day-to-day work first", + source: "onboarding", + }), + ); +}); diff --git a/apps/web/e2e/onboarding-model-required.spec.ts b/apps/web/e2e/onboarding-model-required.spec.ts index bfd722e..224608d 100644 --- a/apps/web/e2e/onboarding-model-required.spec.ts +++ b/apps/web/e2e/onboarding-model-required.spec.ts @@ -37,7 +37,7 @@ test("onboarding shows the look picker when a model is still required", async ({ await page.waitForURL(/\/onboarding/, { timeout: 20_000 }); await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute( "data-onboarding-step", - /^(emoji|preferences|language|voice|integrations)$/, + /^(emoji|language|voice|integrations)$/, { timeout: 30_000 }, ); await expect(page.getByRole("heading", { name: /Design your Oo/i })).toBeVisible({ @@ -56,7 +56,7 @@ for (const unavailable of ["empty", "failed"] as const) { await page.waitForURL(/\/onboarding/, { timeout: 20_000 }); await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute( "data-onboarding-step", - /^(emoji|preferences|language|voice|integrations)$/, + /^(emoji|language|voice|integrations)$/, { timeout: 30_000 }, ); await expect(page.getByRole("heading", { name: /Design your Oo/i })).toBeVisible({ diff --git a/apps/web/e2e/onboarding-oo-swipe.spec.ts b/apps/web/e2e/onboarding-oo-swipe.spec.ts index 8e175f2..0789ace 100644 --- a/apps/web/e2e/onboarding-oo-swipe.spec.ts +++ b/apps/web/e2e/onboarding-oo-swipe.spec.ts @@ -77,5 +77,5 @@ test("mobile onboarding: category pick and swipe look previews", async ({ page } .getByRole("button", { name: /Continue/i }) .first() .click(); - await expect(page.getByRole("heading", { name: /Tell us about yourself/i })).toBeVisible(); + await expect(page.getByRole("heading", { name: /Choose your language/i })).toBeVisible(); }); diff --git a/apps/web/e2e/user-profile.spec.ts b/apps/web/e2e/user-profile.spec.ts index 9b57445..da47b22 100644 --- a/apps/web/e2e/user-profile.spec.ts +++ b/apps/web/e2e/user-profile.spec.ts @@ -1,6 +1,7 @@ /** - * E2E: onboarding answers land in the user profile, and Settings → About you shows every - * fact with its origin, confirms a pending one, and forgets one or all of them. + * E2E: onboarding fills the user profile without asking (time zone, locale and language from + * the browser) and records the focus answer as a fact; Settings → About you shows every fact + * with its origin, confirms a pending one, and forgets one or all of them. */ import { expect, test } from "@playwright/test"; import { captureScreenshot, openUserSettings, rpc, signup, waitForChatComposer } from "./helpers"; @@ -8,7 +9,7 @@ import { captureScreenshot, openUserSettings, rpc, signup, waitForChatComposer } test.use({ timezoneId: "Europe/Vienna" }); test.beforeEach(async ({ page }) => { - // Walk the full emoji-first flow, including the preferences step. + // Walk the full emoji-first flow. await page.route("**/rpc/integrationSetup/get", (route) => route.fulfill({ json: { @@ -34,27 +35,23 @@ test("onboarding feeds the profile; facts can be confirmed and forgotten", async }); await page.getByRole("button", { name: "Continue", exact: true }).click(); - await expect(page.getByRole("heading", { name: /Tell us about yourself/i })).toBeVisible(); - await expect(page.getByLabel("Timezone")).toHaveValue("Europe/Vienna"); - await page.getByPlaceholder("Your name").fill("Pat Example"); - await page.getByRole("button", { name: "Music", exact: true }).click(); - await page.getByRole("button", { name: "Deep-dive focused" }).click(); - await page.getByRole("button", { name: "Direct & concise" }).click(); - await page.getByRole("button", { name: "Continue", exact: true }).click(); - await expect(page.getByRole("heading", { name: /Choose your language/i })).toBeVisible(); await page.getByRole("button", { name: "English", exact: true }).click(); await expect(page.getByRole("heading", { name: /Pick a voice/i })).toBeVisible(); await page.getByRole("button", { name: "Skip", exact: true }).click(); - await page.waitForURL(/\/app\/[^/]+/, { timeout: 30_000 }); + await page.waitForURL(/\/app\/g\/[^/]+/, { timeout: 30_000 }); await waitForChatComposer(page); + // The main bot asks the rest in the main conversation; the focus answer becomes a fact. + await page.getByTestId("focus-choice-day").click(); + await expect(page.locator('[data-testid="choice-card"][data-step="goal"]')).toBeVisible(); // The answers no longer overwrite a bot's instructions. const bots = await rpc>(page, "bots/list", null); expect(bots.length).toBeGreaterThan(0); for (const bot of bots) expect(bot.instructions).not.toContain("User Context"); - expect((await rpc<{ name: string }>(page, "me", null)).name).toBe("Pat Example"); + // The account name is used as is; onboarding no longer asks for it again. + expect((await rpc<{ name: string }>(page, "me", null)).name).toBe("Profile Tester"); // Sensitive facts from anywhere but the user wait for confirmation. await rpc(page, "profile/update", { @@ -67,14 +64,13 @@ test("onboarding feeds the profile; facts can be confirmed and forgotten", async await expect(profile.getByTestId("profile-field-timeZone")).toContainText("Europe/Vienna"); await expect(profile.getByTestId("profile-field-language")).toContainText("English"); await expect(profile.getByTestId("profile-field-locale")).toContainText("United States"); - await expect(profile.getByTestId("profile-field-communicationStyle")).toContainText( - "Direct and concise", - ); - const music = profile.getByTestId("user-fact").filter({ hasText: "Interested in music" }); - await expect(music).toContainText("Onboarding"); - await expect(profile.getByTestId("user-facts-habits")).toContainText( - "Prefers focused deep dives", + const focus = profile + .getByTestId("user-fact") + .filter({ hasText: "Wants help with day-to-day work first" }); + await expect(focus).toContainText("Onboarding"); + await expect(profile.getByTestId("user-facts-goals")).toContainText( + "Wants help with day-to-day work first", ); const allergy = profile.getByTestId("user-fact").filter({ hasText: "Allergic to peanuts" }); @@ -84,8 +80,8 @@ test("onboarding feeds the profile; facts can be confirmed and forgotten", async await expect(allergy).toHaveAttribute("data-status", "active"); await expect(allergy.getByRole("button", { name: "Confirm" })).toHaveCount(0); - await music.getByRole("button", { name: "Delete" }).click(); - await expect(music).toHaveCount(0); + await focus.getByRole("button", { name: "Delete" }).click(); + await expect(focus).toHaveCount(0); await captureScreenshot(page, testInfo, "user-profile-settings"); const afterDelete = await rpc<{ facts: Array<{ value: string; status: string }> }>( page, @@ -93,7 +89,9 @@ test("onboarding feeds the profile; facts can be confirmed and forgotten", async null, ); expect(afterDelete.facts).toEqual( - expect.not.arrayContaining([expect.objectContaining({ value: "Interested in music" })]), + expect.not.arrayContaining([ + expect.objectContaining({ value: "Wants help with day-to-day work first" }), + ]), ); expect(afterDelete.facts).toContainEqual( expect.objectContaining({ value: "Allergic to peanuts", status: "active" }),