From 44323f08eaef92a6bdb8a1b2a6061e035187b523 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 04:48:55 +0000 Subject: [PATCH] Fix: My Work listed tasks from draft/completed/cancelled plans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'My Open Tasks' filtered only by task status, not by the owning plan's status — so a not-started task on a draft plan (never activated) or a leftover open task on a completed/cancelled plan showed up as if it were live work. getTasks now selects codePlans.status as TaskWithContext.planStatus (the query already joins code_plans; this was just missing from the select/return), and My Work filters to planStatus === 'active'. The Tasks page is unaffected — it's the comprehensive review surface and intentionally shows tasks across all plan statuses. Regression tests: planStatus is present and correct for tasks on active and completed plans (207 total, up from 206). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y7FhGxYjnJHJ7m9MpH2dgJ --- app/(dashboard)/my-work/page.tsx | 4 +++- docs/app-spec.md | 4 ++-- lib/db/queries.ts | 3 +++ tests/lib/db/queries.test.ts | 10 +++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/(dashboard)/my-work/page.tsx b/app/(dashboard)/my-work/page.tsx index 794ab3f..89d9c0b 100644 --- a/app/(dashboard)/my-work/page.tsx +++ b/app/(dashboard)/my-work/page.tsx @@ -28,7 +28,9 @@ export default async function MyWorkPage() { ]) const myTasks = allMyTasks - .filter((t) => t.status !== 'done') + // Only active plans are live work — draft plans haven't started, and + // completed/cancelled plans shouldn't nag with stale open tasks. + .filter((t) => t.status !== 'done' && t.planStatus === 'active') .sort((a, b) => (a.endDate ?? '9999') < (b.endDate ?? '9999') ? -1 : 1) const myPlans = plans.filter((p) => p.ownerId === user.id && p.status !== 'completed' && p.status !== 'cancelled') const myItems = items.filter( diff --git a/docs/app-spec.md b/docs/app-spec.md index dec20b7..d043e25 100644 --- a/docs/app-spec.md +++ b/docs/app-spec.md @@ -177,7 +177,7 @@ Provenance columns (`source` default `native`, `connectionId`, `externalId/Key/U | `getProduct(slug, userId)` | `Product & { assets }` \| `null` | Org-aware; assets ordered by createdAt desc; `dependencies` always `[]` | | `getCodePlans(userId, filters?)` | `CodePlan[]` | Org-aware; filters: `productId`, `status`, `type`; includes `taskCount`, `completedTaskCount`, `progress`, `productName` | | `getCodePlan(id, userId)` | `CodePlanDetail` \| `null` | Org-scope guarded; includes full `tasks[]`, `assignees[]`, `targetAssets[]`, `planAssets[]` (per-asset branch/PR); `progress` = % done | -| `getTasks(userId, filters?)` | `TaskWithContext[]` | Org-aware; filters: `planId`, `assigneeId`, `status`; includes `planTitle`, `assetName`, `assigneeName` | +| `getTasks(userId, filters?)` | `TaskWithContext[]` | Org-aware; filters: `planId`, `assigneeId`, `status`; includes `planTitle`, `planStatus`, `assetName`, `assigneeName` | | `getWorkItems(userId, filters?)` | `WorkItemWithContext[]` | Org-aware; filters: `productId`, `assetId`, `type`, `status`, `planId`; includes product/asset names + `linkedPlans[]` | | `getWorkItem(id, userId)` | `WorkItemWithContext` \| `null` | Org-scope guarded | | `getAssetOptions(userId)` | `{id,name,productId}[]` | Flat asset list across accessible products (dropdowns) | @@ -266,7 +266,7 @@ All routes share `AppShell`: 64px top header + 256px sidebar. Sidebar contains: --- #### `/my-work` — My Work -Personal execution view (the Tasks page remains the comprehensive review surface): open tasks assigned to me sorted by end date (overdue in red), plans I own with progress bars, and open work items I own — all respecting the product scope switcher. +Personal execution view (the Tasks page remains the comprehensive review surface): open tasks assigned to me sorted by end date (overdue in red), plans I own with progress bars, and open work items I own — all respecting the product scope switcher. "My Open Tasks" additionally filters to tasks on **active** plans only (via `TaskWithContext.planStatus`) — a task on a draft plan hasn't started, and one on a completed/cancelled plan is a stale artifact, so neither belongs in a personal to-do list. #### `/products` — Products List - Grid of product cards with name, description (truncated), tags (max 3 shown), asset count, active plan count diff --git a/lib/db/queries.ts b/lib/db/queries.ts index fe7877b..d08982e 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -531,6 +531,7 @@ type TaskFilters = { export type TaskWithContext = Task & { planTitle: string + planStatus: CodePlanStatus assetName: string | null assigneeName: string | null } @@ -571,6 +572,7 @@ export async function getTasks(userId: string, filters: TaskFilters = {}): Promi createdAt: tasks.createdAt, updatedAt: tasks.updatedAt, planTitle: codePlans.title, + planStatus: codePlans.status, assetName: assets.name, assigneeName: users.name, }) @@ -602,6 +604,7 @@ export async function getTasks(userId: string, filters: TaskFilters = {}): Promi createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString(), planTitle: r.planTitle, + planStatus: r.planStatus, assetName: r.assetName, assigneeName: r.assigneeName, })) diff --git a/tests/lib/db/queries.test.ts b/tests/lib/db/queries.test.ts index 324f677..b71ae3f 100644 --- a/tests/lib/db/queries.test.ts +++ b/tests/lib/db/queries.test.ts @@ -331,13 +331,21 @@ describe('getTasks', () => { expect(bobTasks[0].id).toBe(F.task1) }) - it('includes planTitle, assetName, and assigneeName', async () => { + it('includes planTitle, planStatus, assetName, and assigneeName', async () => { const taskList = await getTasks(F.alice) const t1 = taskList.find((t) => t.id === F.task1)! expect(t1.planTitle).toBe('Active Plan') + expect(t1.planStatus).toBe('active') expect(t1.assigneeName).toBe('Bob') expect(t1.assetName).toBeNull() // task1 has no assetId }) + + it('reports planStatus: completed for a task on the completed plan', async () => { + // task4 sits on planCompleted in the shared fixtures — the other status + // besides 'active' already present, so this needs no extra seed data. + const completedTasks = await getTasks(F.alice, { planId: F.planCompleted }) + expect(completedTasks[0].planStatus).toBe('completed') + }) }) // ---------------------------------------------------------------------------