From adc8bd185b54935e7a31aae04da3175b7989927a Mon Sep 17 00:00:00 2001
From: Adam Malczewski <github@tradam.dev>
Date: Wed, 20 May 2026 15:04:26 +0900
Subject: feat: phase 3 — config, skills, model groups, task list, and sidebar
 UI
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Config system: TOML-based dispatch.toml with hot-reload via chokidar
- Model/key resolution: tag-based model selection, key fallback chains
- Skills system: directory loader with TOML frontmatter, agent mappings
- Task list tool: add/update/list/get operations with WebSocket events
- API routes: GET /config, /skills, /skills/:name, /models, /models/resolve
- Frontend: sidebar with model status, task list, config viewer, skills browser, permission log
- Sliding sidebar animation using CSS transitions (not Svelte transitions)
---
 .../src/lib/components/TaskListPanel.svelte        | 72 ++++++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 packages/frontend/src/lib/components/TaskListPanel.svelte

(limited to 'packages/frontend/src/lib/components/TaskListPanel.svelte')

diff --git a/packages/frontend/src/lib/components/TaskListPanel.svelte b/packages/frontend/src/lib/components/TaskListPanel.svelte
new file mode 100644
index 0000000..5f2ffe2
--- /dev/null
+++ b/packages/frontend/src/lib/components/TaskListPanel.svelte
@@ -0,0 +1,72 @@
+<script lang="ts">
+	interface TaskItem {
+		id: string;
+		title: string;
+		description: string;
+		status: "pending" | "in_progress" | "done" | "blocked";
+	}
+
+	const { tasks }: { tasks: TaskItem[] } = $props();
+
+	const doneCount = $derived(tasks.filter((t) => t.status === "done").length);
+	const inProgressCount = $derived(tasks.filter((t) => t.status === "in_progress").length);
+
+	function badgeClass(status: TaskItem["status"]): string {
+		switch (status) {
+			case "pending":
+				return "badge badge-ghost badge-xs";
+			case "in_progress":
+				return "badge badge-info badge-xs";
+			case "done":
+				return "badge badge-success badge-xs";
+			case "blocked":
+				return "badge badge-warning badge-xs";
+		}
+	}
+
+	function statusIcon(status: TaskItem["status"]): string {
+		switch (status) {
+			case "pending":
+				return "⏳";
+			case "in_progress":
+				return "▶";
+			case "done":
+				return "✓";
+			case "blocked":
+				return "⚠";
+		}
+	}
+</script>
+
+<div class="flex flex-col gap-2">
+	{#if tasks.length === 0}
+		<p class="text-xs text-base-content/50">No tasks yet.</p>
+	{:else}
+		<p class="text-xs text-base-content/60">
+			{tasks.length} task{tasks.length !== 1 ? "s" : ""}
+			({doneCount} done, {inProgressCount} in progress)
+		</p>
+		<ul class="flex flex-col gap-1">
+			{#each tasks as task (task.id)}
+				<li class="flex flex-col gap-0.5 rounded p-1.5 hover:bg-base-200 transition-colors">
+					<div class="flex items-center gap-1.5">
+						<span class={badgeClass(task.status)}>
+							{statusIcon(task.status)}
+						</span>
+						<span
+							class="text-sm leading-tight {task.status === 'in_progress'
+								? 'font-bold'
+								: 'font-medium'}"
+						>
+							{task.title}
+						</span>
+					</div>
+					{#if task.description}
+						<p class="text-xs text-base-content/60 line-clamp-2 pl-5">{task.description}</p>
+					{/if}
+					<p class="text-xs text-base-content/30 pl-5 font-mono">{task.id}</p>
+				</li>
+			{/each}
+		</ul>
+	{/if}
+</div>
-- 
cgit v1.2.3

