Q208: Home shows one ordered list of what needs you and why work stopped, else the next task in line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ace76dabc7
commit
d422b2ce69
6 files changed
+224
-68
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import { _electron as electron, expect, test } from "@playwright/test";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture";
|
||||
import { closeApp, onboard } from "./onboard";
|
||||
|
||||
test("Home shows one ordered list of what needs you, with why work stopped, and else the next task in line", async () => {
|
||||
const profileDir = join(tmpdir(), `agentcompany-new-e2e-nextup-${randomUUID()}`);
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
const builtinDir = seedBuiltinSkillsFixture(join(tmpdir(), `agentcompany-new-e2e-nextup-builtin-${randomUUID()}`));
|
||||
const app = await electron.launch({
|
||||
args: [resolve("out/main/index.js")],
|
||||
env: { ...process.env, AGENTCOMPANY_NEW_PROFILE_DIR: profileDir, AGENTCOMPANY_NEW_AUTORUN: "0", AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: builtinDir },
|
||||
});
|
||||
try {
|
||||
const page = await app.firstWindow();
|
||||
await onboard(page, "Next Up Co");
|
||||
const blocked = await page.evaluate(async () => {
|
||||
type Api = Record<string, (input?: unknown) => Promise<{ ok: boolean; data: { activeOrgId: string; tasks: Array<{ id: string; title: string }> } }>>;
|
||||
const api = (window as unknown as { agentCompany: Api }).agentCompany;
|
||||
const seeded = (await api.loadSnapshot()).data;
|
||||
const orgId = seeded.activeOrgId;
|
||||
// Onboarding's kickoff task is finished first, so only this test's tasks are open.
|
||||
for (const task of seeded.tasks) await api.updateTaskStatus({ orgId, taskId: task.id, status: "done" });
|
||||
const base = { orgId, description: "d", status: "backlog", assigneeAgentId: null, risk: "low", projectId: null, goalId: null, workspaceId: null, requiresFilesystem: false };
|
||||
await api.createTask({ ...base, title: "Ship pricing page", priority: "normal" });
|
||||
await api.createTask({ ...base, title: "Fix login", priority: "high" });
|
||||
const id = (await api.loadSnapshot()).data.tasks.find((task) => task.title === "Fix login")!.id;
|
||||
await api.setTaskBlocked({ orgId, taskId: id, reason: "Needs the staging key" });
|
||||
return { orgId, id };
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: "Home" }).click();
|
||||
const card = page.getByRole("region", { name: "Needs you" });
|
||||
const stopped = card.getByRole("listitem").filter({ hasText: "Fix login" });
|
||||
await expect(stopped).toContainText("Stopped: Blocked: Needs the staging key");
|
||||
await expect(card.getByText("Ship pricing page")).toHaveCount(0);
|
||||
await expect(page.getByText(/1 thing waiting on you/)).toBeVisible();
|
||||
await stopped.getByRole("button", { name: "Open" }).click();
|
||||
await expect(page.getByRole("dialog", { name: /Fix login/ })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.evaluate(async ({ orgId, id }) => {
|
||||
const api = (window as unknown as { agentCompany: Record<string, (input: unknown) => Promise<unknown>> }).agentCompany;
|
||||
await api.setTaskBlocked({ orgId, taskId: id, reason: null });
|
||||
}, blocked);
|
||||
await page.getByRole("button", { name: "Home" }).click();
|
||||
await expect(card.getByText("You're all caught up")).toBeVisible();
|
||||
// With nothing waiting, the most urgent open task is next.
|
||||
await expect(card.getByText("Next in line")).toBeVisible();
|
||||
await expect(card.getByText("Fix login")).toBeVisible();
|
||||
await expect(card.getByText("Ship pricing page")).toHaveCount(0);
|
||||
} finally {
|
||||
await closeApp(app);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { nextUp } from "../../src/renderer/src/pages/home/next-up";
|
||||
import type { ApprovalRecord, TaskRecord } from "../../src/shared/domain";
|
||||
|
||||
const approval = (id: string, createdAt: string, state = "pending") => ({ id, state, createdAt }) as ApprovalRecord;
|
||||
const task = (id: string, fields: Partial<TaskRecord>) =>
|
||||
({ id, status: "backlog", priority: "normal", hold: null, createdAt: "2026-01-01T00:00:00.000Z", ...fields }) as TaskRecord;
|
||||
const ids = (items: ReturnType<typeof nextUp>["needsYou"]) =>
|
||||
items.map((item) => (item.kind === "approval" ? item.approval.id : item.task.id));
|
||||
|
||||
test("decisions come first, oldest first, then work stopped until you act, most urgent first", () => {
|
||||
const result = nextUp(
|
||||
[approval("apr_new", "2026-01-03T00:00:00.000Z"), approval("apr_old", "2026-01-02T00:00:00.000Z"), approval("apr_done", "2026-01-01T00:00:00.000Z", "approved")],
|
||||
[
|
||||
task("tsk_low", { priority: "low", hold: { reason: "Blocked: needs a key", waitingOn: "operator", resumesAt: null } }),
|
||||
task("tsk_high", { priority: "high", hold: { reason: "Ran out of runs", waitingOn: "operator", resumesAt: null } }),
|
||||
task("tsk_cooldown", { hold: { reason: "Cooling down", waitingOn: "provider", resumesAt: "2026-01-04T00:00:00.000Z" } }),
|
||||
task("tsk_closed", { status: "done", hold: { reason: "Old", waitingOn: "operator", resumesAt: null } }),
|
||||
],
|
||||
);
|
||||
expect(ids(result.needsYou)).toEqual(["apr_old", "apr_new", "tsk_high", "tsk_low"]);
|
||||
const held = result.needsYou[2];
|
||||
expect(held.kind === "held" && held.reason).toBe("Ran out of runs");
|
||||
});
|
||||
|
||||
test("the next task in line is work under way before work not started, then by priority; held, waiting and finished work is not next", () => {
|
||||
const backlogHigh = task("tsk_backlog_high", { priority: "high" });
|
||||
const runningLow = task("tsk_running_low", { status: "in_progress", priority: "low" });
|
||||
const runningHigh = task("tsk_running_high", { status: "in_progress", priority: "high", createdAt: "2026-01-05T00:00:00.000Z" });
|
||||
const review = task("tsk_review", { status: "review", priority: "high" });
|
||||
const cooling = task("tsk_cooling", { status: "in_progress", priority: "high", hold: { reason: "Cooling", waitingOn: "provider", resumesAt: "2026-01-04T00:00:00.000Z" } });
|
||||
expect(nextUp([], [backlogHigh, runningLow, review, cooling]).next).toEqual({ kind: "next", task: runningLow });
|
||||
expect(nextUp([], [backlogHigh, runningLow, runningHigh]).next).toEqual({ kind: "next", task: runningHigh });
|
||||
expect(nextUp([], [backlogHigh]).next).toEqual({ kind: "next", task: backlogHigh });
|
||||
expect(nextUp([], [review, cooling, task("tsk_done", { status: "done" })]).next).toBeNull();
|
||||
});
|
||||
Reference in new issue
Block a user