feat(ui): add MicroSteps component with decompose button, step checklist, and Just Start timer

This commit is contained in:
2026-04-19 10:49:01 +01:00
parent 0bce8e6ec4
commit 442fa6656e

View File

@@ -0,0 +1,111 @@
<script>
import { invoke } from '@tauri-apps/api/core';
import { ListTree, Check, Timer, Loader2 } from 'lucide-svelte';
let { parentTaskId, reduceMotion = false } = $props();
let subtasks = $state([]);
let loading = $state(false);
let error = $state('');
let decomposing = $state(false);
async function loadSubtasks() {
loading = true;
error = '';
try {
subtasks = await invoke('list_subtasks_cmd', { parentTaskId });
} catch (e) {
error = String(e);
} finally {
loading = false;
}
}
async function decompose() {
decomposing = true;
error = '';
try {
subtasks = await invoke('decompose_and_store', { parentTaskId });
} catch (e) {
error = String(e);
} finally {
decomposing = false;
}
}
async function checkStep(subtaskId) {
try {
await invoke('complete_subtask_cmd', { subtaskId });
const idx = subtasks.findIndex(s => s.id === subtaskId);
if (idx >= 0) subtasks[idx] = { ...subtasks[idx], done: true };
} catch (_) {}
}
function startTimer(subtaskId) {
window.dispatchEvent(new CustomEvent('kon:start-timer', {
detail: { taskId: subtaskId, seconds: 120 }
}));
}
$effect(() => {
if (parentTaskId) loadSubtasks();
});
</script>
<div class="pl-6 mt-1 flex flex-col gap-1">
{#if loading}
<div class="flex items-center gap-2 text-[12px] text-text-tertiary py-1">
<Loader2 size={12} class="animate-spin" aria-hidden="true" />
Loading steps…
</div>
{:else if subtasks.length === 0 && !error}
<button
class="flex items-center gap-1.5 text-[12px] text-text-tertiary hover:text-accent py-1 disabled:opacity-50"
onclick={decompose}
disabled={decomposing}
aria-label="Break down this task into micro-steps"
>
{#if decomposing}
<Loader2 size={12} class="animate-spin" aria-hidden="true" />
Breaking down…
{:else}
<ListTree size={12} aria-hidden="true" />
Break down
{/if}
</button>
{:else if error}
<p class="text-[11px] text-text-tertiary py-1">{error}</p>
{:else}
{#each subtasks as step (step.id)}
<div class="flex items-center gap-2 group py-0.5">
<button
class="w-3.5 h-3.5 rounded border flex items-center justify-center flex-shrink-0
{step.done
? 'bg-success/20 border-success text-success'
: 'border-border-subtle hover:border-accent'}"
onclick={() => checkStep(step.id)}
disabled={step.done}
aria-label={step.done ? 'Step complete' : 'Mark step complete'}
>
{#if step.done}
<Check size={9} aria-hidden="true" />
{/if}
</button>
<span class="text-[12px] flex-1 min-w-0 {step.done ? 'line-through text-text-tertiary' : 'text-text-secondary'} truncate">
{step.text}
</span>
{#if !step.done}
<button
class="opacity-0 group-hover:opacity-100 flex items-center gap-1 text-[10px] text-text-tertiary hover:text-accent"
onclick={() => startTimer(step.id)}
aria-label="Start 2-minute timer for this step"
style={reduceMotion ? '' : 'transition: opacity var(--duration-ui)'}
>
<Timer size={10} aria-hidden="true" />
Just Start
</button>
{/if}
</div>
{/each}
{/if}
</div>