diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 14:49:49 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 14:49:49 +0900 |
| commit | ecb001ec7a2e573d8dedf5064e860e5a3e7788fd (patch) | |
| tree | 531371ba6f449a144b5bb45a3d226b025983bd4b /packages/frontend/src/lib/components | |
| parent | 7c527b4d8a72159954405e720d5bf776802dc0ff (diff) | |
| download | dispatch-ecb001ec7a2e573d8dedf5064e860e5a3e7788fd.tar.gz dispatch-ecb001ec7a2e573d8dedf5064e860e5a3e7788fd.zip | |
feat(todo): port opencode's declarative whole-list todo tool
Replace the imperative id-based CRUD todo tool (add/update/list/get/remove)
with opencode's declarative whole-list design: a single `todos` param that
replaces the entire list each call. No model-visible ids, no delta reasoning,
no "task not found" spirals.
- core: TaskItem { id, content, status }; statuses pending|in_progress|
completed|cancelled. TaskList.setTasks/getTasks/onChange. New rich
TODO_DESCRIPTION adapted from opencode's todowrite.txt.
- api: TASK_MANAGEMENT_GUIDANCE system-prompt section (from anthropic.txt);
updated TOOL_DESCRIPTIONS.todo. Reload fix: TabStatusSnapshot now carries
per-tab tasks so getAllStatuses rehydrates the panel on reconnect.
- frontend: mirror types; hydrate tasks from snapshot in both restore paths;
upgrade sidebar Tasks panel to render content + all four statuses + progress.
- tests: new core task-list.test.ts (15); updated api TaskList mocks +
getAllStatuses task-snapshot coverage.
bun run check clean; 569 tests pass; all packages typecheck.
Diffstat (limited to 'packages/frontend/src/lib/components')
| -rw-r--r-- | packages/frontend/src/lib/components/TaskListPanel.svelte | 68 |
1 files changed, 38 insertions, 30 deletions
diff --git a/packages/frontend/src/lib/components/TaskListPanel.svelte b/packages/frontend/src/lib/components/TaskListPanel.svelte index 17ade55..1f84bb8 100644 --- a/packages/frontend/src/lib/components/TaskListPanel.svelte +++ b/packages/frontend/src/lib/components/TaskListPanel.svelte @@ -1,34 +1,55 @@ <script lang="ts"> -interface TaskItem { - id: string; - title: string; - description: string; - status: "pending" | "in_progress" | "done"; -} +import type { TaskItem } from "../types.js"; const { tasks }: { tasks: TaskItem[] } = $props(); -const doneCount = $derived(tasks.filter((t) => t.status === "done").length); +type Status = TaskItem["status"]; + +const completedCount = $derived(tasks.filter((t) => t.status === "completed").length); const inProgressCount = $derived(tasks.filter((t) => t.status === "in_progress").length); +const cancelledCount = $derived(tasks.filter((t) => t.status === "cancelled").length); +// "Active" total excludes cancelled items, so progress reads as work that still counts. +const activeTotal = $derived(tasks.length - cancelledCount); -function checkboxClass(status: TaskItem["status"]): string { +function checkboxClass(status: Status): string { switch (status) { case "pending": return "checkbox checkbox-sm rounded-sm checkbox-secondary"; case "in_progress": return "checkbox checkbox-sm rounded-sm checkbox-info"; - case "done": + case "completed": return "checkbox checkbox-sm rounded-sm checkbox-success"; + case "cancelled": + return "checkbox checkbox-sm rounded-sm checkbox-neutral"; } } -function isChecked(status: TaskItem["status"]): boolean { - return status === "done"; +function isChecked(status: Status): boolean { + return status === "completed"; } -function isIndeterminate(status: TaskItem["status"]): boolean { +function isIndeterminate(status: Status): boolean { return status === "in_progress"; } + +function rowClass(status: Status): string { + if (status === "completed") return "opacity-60"; + if (status === "cancelled") return "opacity-40"; + return ""; +} + +function textClass(status: Status): string { + switch (status) { + case "completed": + return "line-through text-base-content/50"; + case "cancelled": + return "line-through text-base-content/40"; + case "in_progress": + return "font-semibold"; + default: + return ""; + } +} </script> <div class="flex flex-col gap-2"> @@ -36,13 +57,11 @@ function isIndeterminate(status: TaskItem["status"]): boolean { <p class="text-xs text-base-content/50">No tasks yet.</p> {:else} <p class="text-xs text-base-content/60"> - {doneCount}/{tasks.length} done{#if inProgressCount > 0}, {inProgressCount} in progress{/if} + {completedCount}/{activeTotal} completed{#if inProgressCount > 0}, {inProgressCount} in progress{/if}{#if cancelledCount > 0}, {cancelledCount} cancelled{/if} </p> <ul class="flex flex-col gap-0.5"> {#each tasks as task (task.id)} - <li - class="flex items-start gap-2 rounded p-1.5 transition-colors {task.status === 'done' ? 'opacity-60' : ''}" - > + <li class="flex items-start gap-2 rounded p-1.5 transition-colors {rowClass(task.status)}"> <input type="checkbox" class={checkboxClass(task.status)} @@ -51,20 +70,9 @@ function isIndeterminate(status: TaskItem["status"]): boolean { disabled tabindex="-1" /> - <div class="flex flex-col gap-0.5 min-w-0"> - <span - class="text-xs leading-tight {task.status === 'done' - ? 'line-through text-base-content/50' - : task.status === 'in_progress' - ? 'font-semibold' - : ''}" - > - {task.title} - </span> - {#if task.description} - <p class="text-xs text-base-content/50 line-clamp-2">{task.description}</p> - {/if} - </div> + <span class="text-xs leading-tight min-w-0 {textClass(task.status)}"> + {task.content} + </span> </li> {/each} </ul> |
