Files
oikos/web/src/pages/Overview.svelte
dtoro 873b00ac42
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
style(web): fix prettier config, format entire web/ tree
.prettierrc.json was missing "semi": false, so prettier wanted to add
semicolons to a codebase written without them (763 semicolon-free
statements vs. 150 with, in hand-written .ts; zero hand-written .svelte
files use them at all). That's why prettier --check failed on 249 files
— not because the code was unformatted, but because the config didn't
match the actual house style. Added "semi": false; left printWidth/etc
as configured (printWidth barely moves the failure count: 218/213/212
files at 100/120/140).

Ran `prettier --write .` with the corrected config. Verified
semantics-preserving before and after:
- eslint: 142 problems both before and after, byte-identical
- build passes, 38/38 tests pass
- token-stream diff (whitespace/semicolons/quotes normalized) on all
  218 changed files: only 52 had any remaining token change, all either
  trailing-comma removal (matching trailingComma: "none") or import/
  ternary reflow — no semantic changes
- live smoke test: Knowledge, Tasks, Fleet map, and a chat window
  (AgentTrace, markdown, Scope graph, activity rail) all render
  correctly, no console errors

Most of the diff is shadcn/ui vendor files (lib/components/ui/) moving
from the CLI's own style (double quotes, tabs, semicolons) to house
style; re-running `shadcn-svelte add` on a component will need a
follow-up format pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:56:07 +02:00

123 lines
3.9 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte'
import { sessions, loadSessions } from '$lib/stores/chat'
import { openTaskWindow, openNewTaskWindow } from '$lib/stores/windows'
import { liveEvents, subscribeEvents } from '$lib/stores/events'
import { bucket, FILTERS, TASK_EVENTS, heading, type Bucket } from '$lib/tasks'
import { Button } from '$lib/components/ui/button'
import DataTable from '$lib/components/data-table/DataTable.svelte'
import type { DataTableColumn } from '$lib/components/data-table/types'
import StatusDotRenderer from '$lib/components/data-table/renderers/StatusDotRenderer.svelte'
import PlusIcon from '@lucide/svelte/icons/plus'
import type { Session } from '$lib/api'
// ── Task board ──────────────────────────────────────────────────────────
let filter = $state<'all' | Bucket>('all')
const counts = $derived.by(() => {
const c: Record<string, number> = {
all: $sessions.length,
running: 0,
input: 0,
done: 0,
failed: 0
}
for (const s of $sessions) c[bucket(s)]++
return c
})
const visible = $derived(
filter === 'all' ? $sessions : $sessions.filter((s) => bucket(s) === filter)
)
function openTask(s: Session) {
openTaskWindow(s.id, heading(s))
}
onMount(() => {
loadSessions()
const unsubStream = subscribeEvents()
let lastSeenId = 0
let refreshTimer: ReturnType<typeof setTimeout> | null = null
const unsub = liveEvents.subscribe((evs) => {
if (evs.length === 0) return
const maxId = evs[0].id
if (maxId <= lastSeenId) return
const relevant = evs.some((e) => e.id > lastSeenId && TASK_EVENTS.has(e.type))
lastSeenId = maxId
if (relevant) {
if (refreshTimer) clearTimeout(refreshTimer)
refreshTimer = setTimeout(() => loadSessions(), 400)
}
})
return () => {
unsub()
unsubStream()
}
})
const columns: DataTableColumn<Session>[] = [
{
key: '_status',
header: 'Status',
render: StatusDotRenderer,
sortable: true,
accessor: (s) => bucket(s),
width: '140px'
},
{ key: '_heading', header: 'Task', sortable: true, accessor: heading, truncate: true },
{
key: 'summary',
header: 'Summary',
accessor: (s) => s.summary || '—',
truncate: true,
headerClass: 'hidden md:table-cell',
class: 'hidden md:table-cell'
},
{
key: 'last_active_at',
header: 'Last active',
render: 'relative-time',
sortable: true,
width: '112px',
align: 'right'
}
]
const emptyMessage = $derived(
filter === 'all'
? 'No tasks yet. Start one and Nomos will plan it, execute it, and report the outcome.'
: `No ${FILTERS.find((f) => f.id === filter)?.label.toLowerCase()} tasks.`
)
</script>
<div class="relative flex h-full flex-col gap-3 overflow-hidden p-2">
<div class="relative z-10 flex flex-wrap items-center gap-1.5">
{#each FILTERS as f}
<button
type="button"
onclick={() => (filter = f.id)}
class="rounded-full border px-2.5 py-1 text-xs transition-colors {filter === f.id
? 'border-primary bg-primary/10 text-foreground'
: 'border-border bg-card/60 text-muted-foreground backdrop-blur hover:bg-muted/50'}"
>
{f.label}
<span class="ml-1 opacity-60">{counts[f.id] ?? 0}</span>
</button>
{/each}
<div class="flex-1"></div>
<Button size="sm" class="gap-1.5" onclick={openNewTaskWindow}>
<PlusIcon class="size-4" />
New task
</Button>
</div>
<div
class="relative z-10 min-h-0 flex-1 overflow-hidden rounded-xl border bg-card/70 backdrop-blur"
>
<DataTable {columns} data={visible} {emptyMessage} bordered={false} onRowClick={openTask} />
</div>
</div>