Q152: chat replies and goal checkpoints stay out of work lists, load and progress

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 01:26:15 -04:00
1 parent 18f1e1fcc3
commit e8634afb82
8 files changed
+34 -7

No files matched your search

+5 -2
View File
@@ -1,3 +1,4 @@
import { isBusinessTask } from "./work/task-helpers";
import { useMemo, useState, type CSSProperties } from "react";
import { Avatar, Button, Icon, Pill } from "../shared/ui";
import { TopbarActions } from "../shared/TopbarActions";
@@ -26,6 +27,8 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
const organization = requireActiveOrganization(snapshot);
const employee = requireActiveEmployee(snapshot);
const tasks = useMemo(() => snapshot.tasks.filter((task) => task.orgId === orgId), [snapshot.tasks, orgId]);
// Progress and workload count real work only; run titles still resolve against every task.
const businessTasks = useMemo(() => tasks.filter(isBusinessTask), [tasks]);
const agents = useMemo(() => snapshot.agents.filter((agent) => agent.orgId === orgId), [snapshot.agents, orgId]);
const approvals = useMemo(() => snapshot.approvals.filter((item) => item.orgId === orgId), [snapshot.approvals, orgId]);
const goals = useMemo(() => snapshot.goals.filter((item) => item.orgId === orgId), [snapshot.goals, orgId]);
@@ -43,7 +46,7 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
const pendingApprovals = approvals.filter((approval) => approval.state === "pending");
const totalBudgetCents = agents.reduce((sum, agent) => sum + agent.budgetCents, 0);
const primaryGoal = goals.find((goal) => goal.status === "active") ?? goals[0] ?? null;
const goalProgress = computeGoalProgress(primaryGoal, tasks);
const goalProgress = computeGoalProgress(primaryGoal, businessTasks);
const firstName = employee.displayName.split(/\s+/)[0] ?? employee.displayName;
const greeting = greetingFor(new Date().getHours());
@@ -68,7 +71,7 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
</div>
<div style={METRICS}>
<AgentWorkloadChart agents={agents} tasks={tasks} />
<AgentWorkloadChart agents={agents} tasks={businessTasks} />
<RunActivityChart days={usageStats.days} />
<SuccessRateChart days={usageStats.days} />
<CostTrendChart days={usageStats.days} />
+2 -1
View File
@@ -1,3 +1,4 @@
import { isBusinessTask } from "./work/task-helpers";
import { useEffect, useMemo, useRef, useState, type MouseEvent } from "react";
import type { PageProps } from "./types";
import { activeOrgId, requireActiveEmployee } from "./types";
@@ -39,7 +40,7 @@ export function OfficePage({ snapshot }: PageProps) {
const orgAgents = snapshot.agents
.filter((agent) => agent.orgId === orgId && agent.lifecycle === "active")
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
const orgTasks = snapshot.tasks.filter((task) => task.orgId === orgId);
const orgTasks = snapshot.tasks.filter((task) => task.orgId === orgId && isBusinessTask(task));
return orgAgents.map((agent) => ({
id: agent.id,
name: agent.name,
+6 -3
View File
@@ -20,7 +20,7 @@ import { TaskDetailDialog } from "./work/TaskDetailDialog";
import { ProjectOverview } from "./work/ProjectOverview";
import { ListView } from "./work/ListView";
import { TimelineView } from "./work/TimelineView";
import { latestTaskRun, nextStatus, prettyRunState, providerFor, runBlocker, runTone } from "./work/task-helpers";
import { isBusinessTask, latestTaskRun, nextStatus, prettyRunState, providerFor, runBlocker, runTone } from "./work/task-helpers";
import type { PageProps } from "./types";
import { activeOrgId, type Focus } from "./types";
import { t } from "../i18n/i18n";
@@ -52,7 +52,9 @@ export function WorkPage({ snapshot, actions, navigate, focus }: PageProps) {
const [scope, setScope] = useState<Focus | null>(focus && (focus.kind === "goal" || focus.kind === "project") ? focus : null);
const [actionError, setActionError] = useState<string | null>(null);
const allTasks = useMemo(() => snapshot.tasks.filter((task) => task.orgId === orgId), [snapshot.tasks, orgId]);
// The board, its counts and its filters show real work; chat replies and goal checkpoints show on Messages, Home and Runs.
const orgTasks = useMemo(() => snapshot.tasks.filter((task) => task.orgId === orgId), [snapshot.tasks, orgId]);
const allTasks = useMemo(() => orgTasks.filter(isBusinessTask), [orgTasks]);
const tasks = useMemo(
() =>
allTasks.filter((task) => {
@@ -88,7 +90,8 @@ export function WorkPage({ snapshot, actions, navigate, focus }: PageProps) {
setActionError(null);
operation().catch((error: unknown) => setActionError(error instanceof Error ? error.message : String(error)));
};
const detailTask = detailTaskId ? allTasks.find((task) => task.id === detailTaskId) ?? null : null;
// A link (from a run, say) can open an internal task too, though the board does not list it.
const detailTask = detailTaskId ? orgTasks.find((task) => task.id === detailTaskId) ?? null : null;
return (
<div className="page-wide work-page">
@@ -18,6 +18,11 @@ export const WRITING_OPTIONS: ReadonlyArray<{ value: WritingMode; label: string
];
/** Real work: not the vehicle of a chat reply or a goal checkpoint, which the system creates for itself. */
export function isBusinessTask(task: TaskRecord): boolean {
return task.origin !== "chat_wake" && task.origin !== "goal_review";
}
export function nextStatus(status: TaskStatus): TaskStatus | null {
if (status === TASK_STATUS.BACKLOG) return TASK_STATUS.IN_PROGRESS;
if (status === TASK_STATUS.IN_PROGRESS) return TASK_STATUS.REVIEW;
+2 -1
View File
@@ -1,3 +1,4 @@
import { isBusinessTask } from "../pages/work/task-helpers";
import { unreadBySpace } from "../pages/chat/chat-helpers";
import { useEffect, useState, type FormEvent, type ReactNode } from "react";
import type { AppSnapshot, EmployeeRecord } from "../../../shared/domain";
@@ -219,7 +220,7 @@ function computeBadges(snapshot: AppSnapshot, orgId: string): Partial<Record<Rou
const tasks = snapshot.tasks.filter((item) => item.orgId === orgId);
const approvals = snapshot.approvals.filter((item) => item.orgId === orgId);
return {
work: tasks.filter((task) => task.status !== "done").length,
work: tasks.filter((task) => task.status !== "done" && isBusinessTask(task)).length,
chat: employee ? countUnreadMessages(snapshot, orgId, employee.id) : 0,
approvals: approvals.filter((approval) => approval.state === "pending").length,
};
+2
View File
@@ -126,6 +126,8 @@ export interface TaskRecord {
enqueueError: string | null;
/** Why the operator blocked the task; no session starts while it is set. */
blockedReason: string | null;
/** Provenance: a chat reply vehicle, a goal checkpoint, a manager's review of work, or null for plain work. */
origin: "chat_wake" | "goal_review" | "task_review" | null;
/** Why the open task is not moving now, or null when nothing holds it. */
hold: TaskHold | null;
/** Runs at or below this task_runs rowid no longer count toward the run budget. */
+2
View File
@@ -74,6 +74,8 @@ test("the pinned CEO conversation is the Messages front door", async () => {
// From a task, its goal, run and back are one click each.
await page.getByRole("button", { name: "Work" }).click();
// The CEO's reply to the founder is a chat vehicle, not work: it stays off the board.
await expect(page.getByRole("button", { name: /Reply to Yang/ })).toHaveCount(0);
await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click();
const taskDialog = page.getByRole("dialog", { name: "Operationalize goal: Ship the FlowPilot MVP." });
await taskDialog.getByRole("button", { name: "Open goal Ship the FlowPilot MVP." }).click();
+10
View File
@@ -0,0 +1,10 @@
import { describe, expect, test } from "vitest";
import { isBusinessTask } from "../../src/renderer/src/pages/work/task-helpers";
import type { TaskRecord } from "../../src/shared/domain";
describe("what counts as work", () => {
test("chat reply vehicles and goal checkpoints are not work; reviews of work and plain tasks are", () => {
const task = (origin: TaskRecord["origin"]) => ({ origin }) as TaskRecord;
expect([null, "task_review", "chat_wake", "goal_review"].map((origin) => isBusinessTask(task(origin as TaskRecord["origin"])))).toEqual([true, true, false, false]);
});
});