Heute only listed runs and linked to a hard-coded local Mission Control address, so the coach's work had nowhere to show. - Today's brief at the top, habits with a check-off and their streak, and active goals, each only when there is something to show; then the existing needs-you, doing and done lists. - One icon button opens the check-in times (morning, evening, weekly), loaded when opened and saved on every change. - The page reads coach.today, refreshes it when a run finishes or the tab becomes visible, and checks habits off optimistically; the date header uses the user's time zone from the profile. - Removes the Mission Control link and its constant. - Tests: helper unit tests and a web E2E that writes the brief through the main bot, checks a habit off across a reload and sets check-in times (screenshots heute-coach and heute-rhythm). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
128 lines
5.2 KiB
TypeScript
128 lines
5.2 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import { expect, test } from "@playwright/test";
|
|
import type { CoachRhythm, CoachToday, Goal, Habit, Routine } from "@rakazo/contracts";
|
|
import { captureScreenshot, completeOnboarding, rpc, signup } from "./helpers";
|
|
|
|
/** The nav button reads "Heute" or "Heute, N need you". */
|
|
function openHeute(page: Page) {
|
|
return page.getByRole("button", { name: /^Heute(,|$)/ }).click();
|
|
}
|
|
|
|
function shiftDate(date: string, days: number) {
|
|
return new Date(Date.parse(`${date}T00:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10);
|
|
}
|
|
|
|
test("Heute shows native brief instead of Mission Control iframe", async ({ page }, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `heute-${stamp}@rakazo.test`, "password12", "Heute");
|
|
await completeOnboarding(page);
|
|
|
|
await openHeute(page);
|
|
await expect(page).toHaveURL(/\/app\/[^/]+$/);
|
|
|
|
const brief = page.getByTestId("heute-brief");
|
|
await expect(brief).toBeVisible();
|
|
await expect(page.locator('iframe[src="http://127.0.0.1:3088/"]')).toHaveCount(0);
|
|
await expect(page.getByText("evOo Mission Control")).toHaveCount(0);
|
|
await expect(page.getByTestId("heute-mission-control-link")).toHaveCount(0);
|
|
await expect(page.getByTestId("heute-status")).toBeVisible();
|
|
// Nothing tracked yet: no coach sections.
|
|
await expect(page.getByTestId("heute-coach-brief")).toHaveCount(0);
|
|
await expect(page.getByTestId("heute-habits")).toHaveCount(0);
|
|
|
|
await captureScreenshot(page, testInfo, "heute-native-brief");
|
|
});
|
|
|
|
test("Heute shows the coach's brief, habits and goals, checks habits off and sets check-ins", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `heute-coach-${stamp}@rakazo.test`, "password12", "Heute Coach");
|
|
await completeOnboarding(page);
|
|
|
|
// The main bot writes today's brief through its tool.
|
|
const composer = page.getByRole("combobox", { name: "Message" });
|
|
await composer.fill("write today's brief: Two calm hours on the beta, then a walk.");
|
|
await page.keyboard.press("Enter");
|
|
await expect(page.getByText("today's brief is ready.").first()).toBeVisible({ timeout: 30_000 });
|
|
|
|
const { date } = await rpc<CoachToday>(page, "coach/today", {});
|
|
const goal = await rpc<Goal>(page, "coach/goals/create", {
|
|
title: "Ship the beta",
|
|
targetDate: shiftDate(date, 30),
|
|
});
|
|
const habit = await rpc<Habit>(page, "coach/habits/create", { title: "Walk 20 minutes" });
|
|
for (const day of [1, 2]) {
|
|
await rpc(page, "coach/checkIns/log", {
|
|
kind: "habit",
|
|
habitId: habit.id,
|
|
date: shiftDate(date, -day),
|
|
done: true,
|
|
});
|
|
}
|
|
|
|
await openHeute(page);
|
|
await expect(page.getByTestId("heute-coach-brief")).toHaveText(
|
|
"Two calm hours on the beta, then a walk.",
|
|
);
|
|
await expect(page.getByTestId(`heute-goal-${goal.id}`)).toContainText("Ship the beta");
|
|
const habitRow = page.getByTestId(`heute-habit-${habit.id}`);
|
|
await expect(habitRow).toContainText("Walk 20 minutes");
|
|
await expect(page.getByTestId(`heute-habit-streak-${habit.id}`)).toHaveText("2 days");
|
|
|
|
const checkbox = habitRow.getByRole("checkbox");
|
|
await expect(checkbox).not.toBeChecked();
|
|
const logged = page.waitForResponse(
|
|
(response) => response.url().includes("/rpc/coach/checkIns/log") && response.ok(),
|
|
);
|
|
await checkbox.click();
|
|
await logged;
|
|
await expect(checkbox).toBeChecked();
|
|
await expect(page.getByTestId(`heute-habit-streak-${habit.id}`)).toHaveText("3 days");
|
|
await captureScreenshot(page, testInfo, "heute-coach");
|
|
|
|
await page.reload();
|
|
await openHeute(page);
|
|
await expect(page.getByTestId(`heute-habit-${habit.id}`).getByRole("checkbox")).toBeChecked();
|
|
|
|
// Check-in times live behind one compact control.
|
|
await page.getByTestId("heute-rhythm").click();
|
|
const panel = page.getByTestId("heute-rhythm-panel");
|
|
await expect(panel).toBeVisible();
|
|
const morning = panel.getByTestId("heute-rhythm-morning");
|
|
await expect(morning.getByRole("switch")).not.toBeChecked();
|
|
const enabled = page.waitForResponse(
|
|
(response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(),
|
|
);
|
|
await morning.getByRole("switch").click();
|
|
await enabled;
|
|
const time = morning.locator('input[type="time"]');
|
|
await expect(time).toHaveValue("08:00");
|
|
const moved = page.waitForResponse(
|
|
(response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(),
|
|
);
|
|
await time.fill("07:30");
|
|
await moved;
|
|
await expect(time).toHaveValue("07:30");
|
|
const weekly = panel.getByTestId("heute-rhythm-weekly");
|
|
const weeklyOn = page.waitForResponse(
|
|
(response) => response.url().includes("/rpc/coach/rhythm/set") && response.ok(),
|
|
);
|
|
await weekly.getByRole("switch").click();
|
|
await weeklyOn;
|
|
await expect(weekly.getByRole("combobox")).toHaveValue("0");
|
|
await captureScreenshot(page, testInfo, "heute-rhythm");
|
|
|
|
expect(await rpc<CoachRhythm>(page, "coach/rhythm/get", {})).toEqual({
|
|
morning: "07:30",
|
|
evening: null,
|
|
weekly: { day: 0, time: "18:00" },
|
|
});
|
|
const main = await rpc<{ id: string }>(page, "bots/ensureMain", {});
|
|
const routines = await rpc<Routine[]>(page, "routines/list", { botId: main.id });
|
|
expect(routines.map((routine) => routine.name).sort()).toEqual([
|
|
"Morning brief",
|
|
"Weekly review",
|
|
]);
|
|
});
|