style(web): fix prettier config, format entire web/ tree
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

.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>
This commit is contained in:
2026-07-27 12:56:07 +02:00
parent b345783eef
commit 873b00ac42
217 changed files with 4945 additions and 3538 deletions

View File

@@ -60,10 +60,16 @@
let wasStreaming = $state(false)
$effect(() => {
if (streaming) { indicatorDone = false; wasStreaming = true }
if (streaming) {
indicatorDone = false
wasStreaming = true
}
if (!streaming && wasStreaming) {
indicatorDone = true
const t = setTimeout(() => { indicatorDone = false; wasStreaming = false }, 3000)
const t = setTimeout(() => {
indicatorDone = false
wasStreaming = false
}, 3000)
return () => clearTimeout(t)
}
})
@@ -91,7 +97,10 @@
const lineHeight = parseFloat(taCs.lineHeight)
if (!Number.isFinite(lineHeight)) return
const taBoxY =
parseFloat(taCs.paddingTop) + parseFloat(taCs.paddingBottom) + parseFloat(taCs.borderTopWidth) + parseFloat(taCs.borderBottomWidth)
parseFloat(taCs.paddingTop) +
parseFloat(taCs.paddingBottom) +
parseFloat(taCs.borderTopWidth) +
parseFloat(taCs.borderBottomWidth)
// The wrapper's own padding/border (space around the textarea, not part
// of it) also has to fit inside the minimum, or the textarea gets
// squeezed below one line once the pane is dragged down to it.
@@ -144,7 +153,9 @@
}
renderer.table = function (token) {
const header = token.header.map((c: { text: string }) => `<th>${c.text}</th>`).join('')
const body = token.rows.map((r: { text: string }[]) => `<tr>${r.map((c) => `<td>${c.text}</td>`).join('')}</tr>`).join('')
const body = token.rows
.map((r: { text: string }[]) => `<tr>${r.map((c) => `<td>${c.text}</td>`).join('')}</tr>`)
.join('')
return `<div class="table-wrapper"><table><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table></div>`
}
return DOMPurify.sanitize(marked.parse(text, { async: false, renderer }) as string)
@@ -181,126 +192,172 @@
</script>
<div class="flex h-full min-h-0 min-w-0 flex-col" bind:clientHeight={threadHeight}>
<Splitpanes horizontal theme="oikos-theme" dblClickSplitter={false} class="min-h-0 flex-1" on:resize={() => (userResizedInput = true)}>
<Splitpanes
horizontal
theme="oikos-theme"
dblClickSplitter={false}
class="min-h-0 flex-1"
on:resize={() => (userResizedInput = true)}
>
<Pane class="flex flex-col">
<div class="min-h-0 flex-1 overflow-y-auto" bind:this={container} onscroll={onScroll}>
<div class="mx-auto flex min-h-full max-w-3xl flex-col gap-5 p-4">
{#if messages.length === 0}
<div class="flex flex-1 flex-col items-center justify-center gap-6 text-center">
<div>
<h2 class="text-xl font-semibold">Nomos</h2>
<p class="mt-1 text-sm text-muted-foreground">Your resident operator. Ask about the fleet, or tell it to act.</p>
</div>
{#if suggestions.length}
<div class="grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2">
{#each suggestions as q}
<Button variant="outline" size="sm" class="h-auto justify-start whitespace-normal py-2 text-left text-xs" onclick={() => ask(q)}>
{q}
</Button>
{/each}
</div>
{/if}
</div>
{/if}
{#each messages as msg, idx (msg.id)}
<div class="flex flex-col gap-1.5 {msg.role === 'user' ? 'items-end' : 'items-start'}">
{#if msg.role === 'user'}
<div class="flex items-baseline gap-2 px-1">
<span class="text-[10px] font-medium text-muted-foreground/70">You</span>
{#if msg.created_at}
<span class="text-[9px] text-muted-foreground/50">{formatTime(msg.created_at)}</span>
{/if}
</div>
<div class="max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-4 py-2.5 text-sm text-primary-foreground whitespace-pre-wrap user-msg">{msg.text}</div>
{:else}
{@const isLast = idx === messages.length - 1}
{@const traceStatus = !isLast
? 'idle'
: error
? 'error'
: streaming
? 'running'
: indicatorDone
? 'done'
: 'idle'}
<div class="flex w-full flex-col gap-2">
<div class="flex items-baseline gap-2 px-1">
<span class="text-[10px] font-medium text-muted-foreground/70">Nomos</span>
{#if msg.created_at}
<span class="text-[9px] text-muted-foreground/50">{formatTime(msg.created_at)}</span>
{/if}
<div class="min-h-0 flex-1 overflow-y-auto" bind:this={container} onscroll={onScroll}>
<div class="mx-auto flex min-h-full max-w-3xl flex-col gap-5 p-4">
{#if messages.length === 0}
<div class="flex flex-1 flex-col items-center justify-center gap-6 text-center">
<div>
<h2 class="text-xl font-semibold">Nomos</h2>
<p class="mt-1 text-sm text-muted-foreground">
Your resident operator. Ask about the fleet, or tell it to act.
</p>
</div>
<!-- The working trace sits above the answer: it's what happened
first, and collapsed it keeps a long tool run from burying
the text below it. -->
{#if msg.tools.length > 0 || traceStatus !== 'idle'}
<AgentTrace
tools={msg.tools}
status={traceStatus}
label={traceStatus === 'idle' ? null : indicatorLabel}
/>
{/if}
{#if msg.text}
<div class="prose-chat max-w-none text-sm leading-relaxed assistant-msg">
<!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized via DOMPurify -->
{@html render(msg.text)}
{#if isLast && streaming}
<span class="stream-cursor" aria-hidden="true"></span>
{/if}
{#if suggestions.length}
<div class="grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2">
{#each suggestions as q}
<Button
variant="outline"
size="sm"
class="h-auto justify-start whitespace-normal py-2 text-left text-xs"
onclick={() => ask(q)}
>
{q}
</Button>
{/each}
</div>
{/if}
</div>
{/if}
{#each messages as msg, idx (msg.id)}
<div class="flex flex-col gap-1.5 {msg.role === 'user' ? 'items-end' : 'items-start'}">
{#if msg.role === 'user'}
<div class="flex items-baseline gap-2 px-1">
<span class="text-[10px] font-medium text-muted-foreground/70">You</span>
{#if msg.created_at}
<span class="text-[9px] text-muted-foreground/50"
>{formatTime(msg.created_at)}</span
>
{/if}
</div>
<div
class="max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-4 py-2.5 text-sm text-primary-foreground whitespace-pre-wrap user-msg"
>
{msg.text}
</div>
{:else}
{@const isLast = idx === messages.length - 1}
{@const traceStatus = !isLast
? 'idle'
: error
? 'error'
: streaming
? 'running'
: indicatorDone
? 'done'
: 'idle'}
<div class="flex w-full flex-col gap-2">
<div class="flex items-baseline gap-2 px-1">
<span class="text-[10px] font-medium text-muted-foreground/70">Nomos</span>
{#if msg.created_at}
<span class="text-[9px] text-muted-foreground/50"
>{formatTime(msg.created_at)}</span
>
{/if}
</div>
<!-- The working trace sits above the answer: it's what happened
first, and collapsed it keeps a long tool run from burying
the text below it. -->
{#if msg.tools.length > 0 || traceStatus !== 'idle'}
<AgentTrace
tools={msg.tools}
status={traceStatus}
label={traceStatus === 'idle' ? null : indicatorLabel}
/>
{/if}
{#if msg.text}
<div class="prose-chat max-w-none text-sm leading-relaxed assistant-msg">
<!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized via DOMPurify -->
{@html render(msg.text)}
{#if isLast && streaming}
<span class="stream-cursor" aria-hidden="true"></span>
{/if}
</div>
{/if}
</div>
{/if}
</div>
{/each}
{#if question}
<OperatorQuestion {sessionId} {question} />
{/if}
<div bind:this={messagesEnd}></div>
</div>
</div>
{#if connectionState === 'disconnected'}
<div class="mx-auto w-full max-w-3xl px-4">
<div
class="mb-2 flex items-center gap-2 rounded-md border border-warning/50 bg-warning/10 px-3 py-2 text-xs"
>
<RefreshCwIcon class="size-3 shrink-0" aria-hidden="true" />
<span class="text-warning-foreground flex-1"
>Agent connection lost. The task may still be running.</span
>
<Button size="xs" variant="outline" class="h-6 text-[11px]" onclick={onReconnect}
>Reconnect</Button
>
</div>
</div>
{:else if connectionState === 'reconnecting'}
<div class="mx-auto w-full max-w-3xl px-4">
<div class="mb-2 flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-2 text-xs">
<RefreshCwIcon
class="size-3 shrink-0 animate-spin text-muted-foreground"
aria-hidden="true"
/>
<span class="text-muted-foreground flex-1">Reconnecting to agent…</span>
</div>
</div>
{/if}
{#if error}
<div class="mx-auto w-full max-w-3xl px-4">
<div
class="mb-2 rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-xs text-destructive"
>
{error}
</div>
</div>
{/if}
{#each chatErrors as err (err.id)}
<div class="mx-auto w-full max-w-3xl px-4">
<div
class="mb-2 flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive"
>
<span class="flex-1">{err.message}</span>
{#if err.action}
<Button
size="xs"
variant="ghost"
class="h-6 text-[11px]"
onclick={() => onDismissError(err.id)}>{err.action}</Button
>
{/if}
<button
class="ml-1 text-muted-foreground hover:text-foreground"
onclick={() => onDismissError(err.id)}
aria-label="Dismiss">×</button
>
</div>
</div>
{/each}
{#if question}
<OperatorQuestion {sessionId} {question} />
{/if}
<div bind:this={messagesEnd}></div>
</div>
</div>
{#if connectionState === 'disconnected'}
<div class="mx-auto w-full max-w-3xl px-4">
<div class="mb-2 flex items-center gap-2 rounded-md border border-warning/50 bg-warning/10 px-3 py-2 text-xs">
<RefreshCwIcon class="size-3 shrink-0" aria-hidden="true" />
<span class="text-warning-foreground flex-1">Agent connection lost. The task may still be running.</span>
<Button size="xs" variant="outline" class="h-6 text-[11px]" onclick={onReconnect}>Reconnect</Button>
</div>
</div>
{:else if connectionState === 'reconnecting'}
<div class="mx-auto w-full max-w-3xl px-4">
<div class="mb-2 flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-2 text-xs">
<RefreshCwIcon class="size-3 shrink-0 animate-spin text-muted-foreground" aria-hidden="true" />
<span class="text-muted-foreground flex-1">Reconnecting to agent…</span>
</div>
</div>
{/if}
{#if error}
<div class="mx-auto w-full max-w-3xl px-4">
<div class="mb-2 rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-xs text-destructive">
{error}
</div>
</div>
{/if}
{#each chatErrors as err (err.id)}
<div class="mx-auto w-full max-w-3xl px-4">
<div class="mb-2 flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive">
<span class="flex-1">{err.message}</span>
{#if err.action}
<Button size="xs" variant="ghost" class="h-6 text-[11px]" onclick={() => onDismissError(err.id)}>{err.action}</Button>
{/if}
<button class="ml-1 text-muted-foreground hover:text-foreground" onclick={() => onDismissError(err.id)} aria-label="Dismiss">×</button>
</div>
</div>
{/each}
</Pane>
<Pane bind:size={inputSize} minSize={inputMinSize} maxSize={45} class="flex flex-col">
<div class="flex h-full min-h-0 flex-col border-t bg-card/50 p-3 input-ornament relative" bind:this={inputWrapperRef}>
<div
class="flex h-full min-h-0 flex-col border-t bg-card/50 p-3 input-ornament relative"
bind:this={inputWrapperRef}
>
<form
class="relative mx-auto flex h-full w-full max-w-3xl"
onsubmit={(e) => {
@@ -514,7 +571,13 @@
border: none;
height: 1px;
margin: 0.75rem 0;
background: linear-gradient(to right, transparent, var(--border) 20%, var(--border) 80%, transparent);
background: linear-gradient(
to right,
transparent,
var(--border) 20%,
var(--border) 80%,
transparent
);
}
/* Bold is emphasis, not color — dark weight reads cleanly and lets the
@@ -559,7 +622,9 @@
border-radius: 0.375rem;
color: var(--muted-foreground);
opacity: 0;
transition: opacity 0.15s, color 0.15s;
transition:
opacity 0.15s,
color 0.15s;
cursor: pointer;
border: none;
background: transparent;
@@ -586,7 +651,12 @@
}
@keyframes cursor-blink {
0%, 100% { opacity: 0.75; }
50% { opacity: 0; }
0%,
100% {
opacity: 0.75;
}
50% {
opacity: 0;
}
}
</style>