day 1: Kin from zero to production - agents, GUI, autopilot, 352 tests

This commit is contained in:
Gros Frumos 2026-03-15 23:22:49 +02:00
parent 8d9facda4f
commit 8a6f280cbd
22 changed files with 1907 additions and 103 deletions

View file

@ -15,7 +15,28 @@ const error = ref('')
const activeTab = ref<'tasks' | 'decisions' | 'modules'>('tasks')
// Filters
const taskStatusFilter = ref((route.query.status as string) || '')
const ALL_TASK_STATUSES = ['pending', 'in_progress', 'review', 'blocked', 'decomposed', 'done', 'cancelled']
function initStatusFilter(): string[] {
const q = route.query.status as string
if (q) return q.split(',').filter((s: string) => s)
const stored = localStorage.getItem(`kin-task-statuses-${props.id}`)
if (stored) { try { return JSON.parse(stored) } catch {} }
return []
}
const selectedStatuses = ref<string[]>(initStatusFilter())
function toggleStatus(s: string) {
const idx = selectedStatuses.value.indexOf(s)
if (idx >= 0) selectedStatuses.value.splice(idx, 1)
else selectedStatuses.value.push(s)
}
function clearStatusFilter() {
selectedStatuses.value = []
}
const decisionTypeFilter = ref('')
const decisionSearch = ref('')
@ -98,16 +119,17 @@ async function load() {
}
}
watch(taskStatusFilter, (val) => {
router.replace({ query: { ...route.query, status: val || undefined } })
})
watch(selectedStatuses, (val) => {
localStorage.setItem(`kin-task-statuses-${props.id}`, JSON.stringify(val))
router.replace({ query: { ...route.query, status: val.length ? val.join(',') : undefined } })
}, { deep: true })
onMounted(() => { load(); loadMode() })
const filteredTasks = computed(() => {
if (!project.value) return []
let tasks = project.value.tasks
if (taskStatusFilter.value) tasks = tasks.filter(t => t.status === taskStatusFilter.value)
if (selectedStatuses.value.length > 0) tasks = tasks.filter(t => selectedStatuses.value.includes(t.status))
return tasks
})
@ -145,12 +167,6 @@ function modTypeColor(t: string) {
return m[t] || 'gray'
}
const taskStatuses = computed(() => {
if (!project.value) return []
const s = new Set(project.value.tasks.map(t => t.status))
return Array.from(s).sort()
})
const decTypes = computed(() => {
if (!project.value) return []
const s = new Set(project.value.decisions.map(d => d.type))
@ -179,7 +195,7 @@ async function runTask(taskId: string, event: Event) {
event.stopPropagation()
if (!confirm(`Run pipeline for ${taskId}?`)) return
try {
await api.runTask(taskId, autoMode.value)
await api.runTask(taskId)
await load()
} catch (e: any) {
error.value = e.message
@ -253,12 +269,17 @@ async function addDecision() {
<!-- Tasks Tab -->
<div v-if="activeTab === 'tasks'">
<div class="flex items-center justify-between mb-3">
<div class="flex gap-2">
<select v-model="taskStatusFilter"
class="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-300">
<option value="">All statuses</option>
<option v-for="s in taskStatuses" :key="s" :value="s">{{ s }}</option>
</select>
<div class="flex gap-1 flex-wrap items-center">
<button v-for="s in ALL_TASK_STATUSES" :key="s"
:data-status="s"
@click="toggleStatus(s)"
class="px-2 py-0.5 text-xs rounded border transition-colors"
:class="selectedStatuses.includes(s)
? 'bg-blue-900/40 text-blue-300 border-blue-700'
: 'bg-gray-900 text-gray-600 border-gray-800 hover:text-gray-400 hover:border-gray-700'"
>{{ s }}</button>
<button v-if="selectedStatuses.length" data-action="clear-status" @click="clearStatusFilter"
class="px-1.5 py-0.5 text-xs text-gray-600 hover:text-red-400 rounded"></button>
</div>
<div class="flex gap-2">
<button @click="toggleMode"
@ -284,7 +305,7 @@ async function addDecision() {
<div v-if="filteredTasks.length === 0" class="text-gray-600 text-sm">No tasks.</div>
<div v-else class="space-y-1">
<router-link v-for="t in filteredTasks" :key="t.id"
:to="{ path: `/task/${t.id}`, query: taskStatusFilter ? { back_status: taskStatusFilter } : undefined }"
:to="{ path: `/task/${t.id}`, query: selectedStatuses.length ? { back_status: selectedStatuses.join(',') } : undefined }"
class="flex items-center justify-between px-3 py-2 border border-gray-800 rounded text-sm hover:border-gray-600 no-underline block transition-colors">
<div class="flex items-center gap-2 min-w-0">
<span class="text-gray-500 shrink-0 w-24">{{ t.id }}</span>