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

@@ -11,7 +11,14 @@
import { Separator } from '$lib/components/ui/separator'
import { Checkbox } from '$lib/components/ui/checkbox'
import { Slider } from '$lib/components/ui/slider'
import { fetchWithAuth, setConfig, initConfig, getConfig, clearConfig, type OikosConfig } from '$lib/config'
import {
fetchWithAuth,
setConfig,
initConfig,
getConfig,
clearConfig,
type OikosConfig
} from '$lib/config'
import { startLogin, logout as oidcLogout, getUser, isOIDCConfigured } from '$lib/oidc'
import { getTheme, setTheme, THEME_LABELS, type Theme } from '$lib/stores/theme.svelte'
import {
@@ -138,234 +145,302 @@
</script>
<div class="flex h-full min-h-0 flex-col">
<div class="flex min-h-0 flex-1">
<nav class="flex w-44 shrink-0 flex-col gap-0.5 border-r bg-muted/20 p-2">
{#each SECTIONS as s (s.id)}
<button
type="button"
class="flex items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors {section === s.id
? 'bg-muted font-medium text-foreground'
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'}"
onclick={() => (section = s.id)}
>
<s.icon class="size-4 shrink-0" />
{s.label}
</button>
{/each}
</nav>
<div class="flex min-h-0 flex-1">
<nav class="flex w-44 shrink-0 flex-col gap-0.5 border-r bg-muted/20 p-2">
{#each SECTIONS as s (s.id)}
<button
type="button"
class="flex items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors {section ===
s.id
? 'bg-muted font-medium text-foreground'
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'}"
onclick={() => (section = s.id)}
>
<s.icon class="size-4 shrink-0" />
{s.label}
</button>
{/each}
</nav>
<div class="min-h-0 flex-1 overflow-y-auto p-6">
{#if section === 'connection'}
<div class="mx-auto flex max-w-md flex-col gap-5">
<div>
<h2 class="text-sm font-semibold">Connection</h2>
<p class="mt-0.5 text-xs text-muted-foreground">Where the control room talks to your Oikos server.</p>
</div>
{#if oidcUser}
<p class="text-xs text-muted-foreground">
Signed in via Authentik as <span class="font-medium text-foreground">{oidcUser}</span>
</p>
{/if}
<div class="flex flex-col gap-1.5">
<Label for="settings-server-url" class="text-xs font-medium">Server URL</Label>
<div class="relative">
<ServerIcon class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/50" />
<Input id="settings-server-url" type="url" placeholder="https://oikos.hubris.network" class="pl-9" bind:value={apiUrl} />
<div class="min-h-0 flex-1 overflow-y-auto p-6">
{#if section === 'connection'}
<div class="mx-auto flex max-w-md flex-col gap-5">
<div>
<h2 class="text-sm font-semibold">Connection</h2>
<p class="mt-0.5 text-xs text-muted-foreground">
Where the control room talks to your Oikos server.
</p>
</div>
</div>
<Button type="button" variant="secondary" disabled={oidcLoggingIn} onclick={loginWithAuthentik} class="w-full gap-2">
<LogInIcon class="size-4" />
{oidcLoggingIn ? 'Redirecting…' : oidcConfigured ? 'Switch account with Authentik' : 'Login with Authentik'}
</Button>
{#if oidcUser}
<p class="text-xs text-muted-foreground">
Signed in via Authentik as <span class="font-medium text-foreground">{oidcUser}</span>
</p>
{/if}
<div class="flex items-center gap-3">
<Separator decorative class="flex-1" />
<span class="text-[10px] font-medium uppercase tracking-widest text-muted-foreground/40">or use</span>
<Separator decorative class="flex-1" />
</div>
<form class="flex flex-col gap-2.5" onsubmit={(e) => { e.preventDefault(); save() }}>
<div class="relative">
<LockIcon class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/50" />
<Input type="password" placeholder="Bearer token" class="pl-9" bind:value={token} />
<div class="flex flex-col gap-1.5">
<Label for="settings-server-url" class="text-xs font-medium">Server URL</Label>
<div class="relative">
<ServerIcon
class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/50"
/>
<Input
id="settings-server-url"
type="url"
placeholder="https://oikos.hubris.network"
class="pl-9"
bind:value={apiUrl}
/>
</div>
</div>
<Button type="submit" disabled={saving} class="w-full">
{saving ? 'Saving…' : 'Save'}
<Button
type="button"
variant="secondary"
disabled={oidcLoggingIn}
onclick={loginWithAuthentik}
class="w-full gap-2"
>
<LogInIcon class="size-4" />
{oidcLoggingIn
? 'Redirecting…'
: oidcConfigured
? 'Switch account with Authentik'
: 'Login with Authentik'}
</Button>
</form>
{#if error}
<p class="rounded-lg bg-destructive/10 px-3 py-2 text-center text-sm text-destructive">{error}</p>
{/if}
{#if existing.token || oidcConfigured}
<div class="flex justify-center pt-1">
<Button type="button" variant="ghost" size="sm" onclick={forgetConnection}>Forget saved connection</Button>
<div class="flex items-center gap-3">
<Separator decorative class="flex-1" />
<span class="text-[10px] font-medium uppercase tracking-widest text-muted-foreground/40"
>or use</span
>
<Separator decorative class="flex-1" />
</div>
{/if}
</div>
{:else if section === 'appearance'}
<div class="mx-auto flex max-w-md flex-col gap-5">
<div>
<h2 class="text-sm font-semibold">Appearance</h2>
<p class="mt-0.5 text-xs text-muted-foreground">Pick the theme for the whole desktop.</p>
<form
class="flex flex-col gap-2.5"
onsubmit={(e) => {
e.preventDefault()
save()
}}
>
<div class="relative">
<LockIcon
class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/50"
/>
<Input type="password" placeholder="Bearer token" class="pl-9" bind:value={token} />
</div>
<Button type="submit" disabled={saving} class="w-full">
{saving ? 'Saving…' : 'Save'}
</Button>
</form>
{#if error}
<p class="rounded-lg bg-destructive/10 px-3 py-2 text-center text-sm text-destructive">
{error}
</p>
{/if}
{#if existing.token || oidcConfigured}
<div class="flex justify-center pt-1">
<Button type="button" variant="ghost" size="sm" onclick={forgetConnection}
>Forget saved connection</Button
>
</div>
{/if}
</div>
{:else if section === 'appearance'}
<div class="mx-auto flex max-w-md flex-col gap-5">
<div>
<h2 class="text-sm font-semibold">Appearance</h2>
<p class="mt-0.5 text-xs text-muted-foreground">
Pick the theme for the whole desktop.
</p>
</div>
<div class="grid grid-cols-2 gap-2">
{#each Object.entries(THEME_LABELS) as [id, label] (id)}
<button
type="button"
class="flex items-center justify-between gap-1.5 rounded-lg border px-2.5 py-1.5 text-left text-sm transition-colors {getTheme() === (id as Theme)
? 'border-primary/50 bg-primary/5 text-foreground'
: 'text-muted-foreground hover:bg-muted/50'}"
onclick={() => pickTheme(id as Theme)}
>
{label}
{#if getTheme() === (id as Theme)}<CheckIcon class="size-3.5 shrink-0 text-primary" />{/if}
</button>
{/each}
</div>
<div class="grid grid-cols-2 gap-2">
{#each Object.entries(THEME_LABELS) as [id, label] (id)}
<button
type="button"
class="flex items-center justify-between gap-1.5 rounded-lg border px-2.5 py-1.5 text-left text-sm transition-colors {getTheme() ===
(id as Theme)
? 'border-primary/50 bg-primary/5 text-foreground'
: 'text-muted-foreground hover:bg-muted/50'}"
onclick={() => pickTheme(id as Theme)}
>
{label}
{#if getTheme() === (id as Theme)}<CheckIcon
class="size-3.5 shrink-0 text-primary"
/>{/if}
</button>
{/each}
</div>
<Separator decorative />
<Separator decorative />
<div>
<h3 class="text-sm font-semibold">Desktop background</h3>
<p class="mt-0.5 text-xs text-muted-foreground">
A subtle CSS pattern behind your icons, in the style of
<a href="https://www.magicpattern.design/tools/css-backgrounds" target="_blank" rel="noreferrer">magicpattern.design</a>.
</p>
</div>
<div>
<h3 class="text-sm font-semibold">Desktop background</h3>
<p class="mt-0.5 text-xs text-muted-foreground">
A subtle CSS pattern behind your icons, in the style of
<a
href="https://www.magicpattern.design/tools/css-backgrounds"
target="_blank"
rel="noreferrer">magicpattern.design</a
>.
</p>
</div>
<div class="grid grid-cols-4 gap-2">
{#each PATTERNS as p (p.id)}
{@const active = getBackground().pattern === p.id}
<button
type="button"
class="flex flex-col items-center gap-1.5 rounded-lg border p-2 transition-colors {active
? 'border-primary/60 bg-primary/5'
: 'border-border hover:bg-muted/50'}"
onclick={() => setBackgroundPattern(p.id)}
title={p.label}
>
<span
class="h-10 w-full rounded-md border border-border/60 bg-muted"
style={swatchStyle(p.id, getBackground().color)}
></span>
<span class="flex items-center gap-1 text-[11px] {active ? 'font-medium text-foreground' : 'text-muted-foreground'}">
{p.label}
{#if active}<CheckIcon class="size-3 text-primary" />{/if}
</span>
</button>
{/each}
</div>
<div class="grid grid-cols-4 gap-2">
{#each PATTERNS as p (p.id)}
{@const active = getBackground().pattern === p.id}
<button
type="button"
class="flex flex-col items-center gap-1.5 rounded-lg border p-2 transition-colors {active
? 'border-primary/60 bg-primary/5'
: 'border-border hover:bg-muted/50'}"
onclick={() => setBackgroundPattern(p.id)}
title={p.label}
>
<span
class="h-10 w-full rounded-md border border-border/60 bg-muted"
style={swatchStyle(p.id, getBackground().color)}
></span>
<span
class="flex items-center gap-1 text-[11px] {active
? 'font-medium text-foreground'
: 'text-muted-foreground'}"
>
{p.label}
{#if active}<CheckIcon class="size-3 text-primary" />{/if}
</span>
</button>
{/each}
</div>
<!-- Colors: pattern (foreground shapes) + an optional fill behind
<!-- Colors: pattern (foreground shapes) + an optional fill behind
them. Fill defaults to "Auto", i.e. transparent, so the app's
own theme background shows through the gaps — same as before
this control existed. -->
<div class="flex items-center justify-between gap-4">
<Label for="bg-color" class="text-xs font-medium">Pattern color</Label>
<div class="flex items-center gap-2">
<input
id="bg-color"
type="color"
class="size-7 cursor-pointer rounded-md border border-border bg-transparent p-0.5"
value={getBackground().color}
oninput={(e) => setPatternColor((e.currentTarget as HTMLInputElement).value)}
<div class="flex items-center justify-between gap-4">
<Label for="bg-color" class="text-xs font-medium">Pattern color</Label>
<div class="flex items-center gap-2">
<input
id="bg-color"
type="color"
class="size-7 cursor-pointer rounded-md border border-border bg-transparent p-0.5"
value={getBackground().color}
oninput={(e) => setPatternColor((e.currentTarget as HTMLInputElement).value)}
/>
<span class="font-mono text-xs text-muted-foreground">{getBackground().color}</span>
</div>
</div>
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-2">
<Checkbox
id="bg-fill-toggle"
checked={getBackground().fillColor !== null}
onCheckedChange={(v) => onFillToggle(!!v)}
/>
<Label for="bg-fill-toggle" class="text-xs font-medium">Custom background fill</Label>
</div>
<div class="flex items-center gap-2">
<input
id="bg-fill-color"
type="color"
class="size-7 cursor-pointer rounded-md border border-border bg-transparent p-0.5 disabled:cursor-not-allowed disabled:opacity-40"
value={getBackground().fillColor ?? fillDraft}
disabled={getBackground().fillColor === null}
oninput={onFillColorInput}
/>
<span class="font-mono text-xs text-muted-foreground"
>{getBackground().fillColor ?? 'Auto'}</span
>
</div>
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-opacity" class="text-xs font-medium">Opacity</Label>
<span class="font-mono text-xs text-muted-foreground"
>{Math.round(getBackground().opacity * 100)}%</span
>
</div>
<Slider
id="bg-opacity"
type="single"
min={0}
max={1}
step={0.01}
value={getBackground().opacity}
onValueChange={setBackgroundOpacity}
/>
<span class="font-mono text-xs text-muted-foreground">{getBackground().color}</span>
</div>
</div>
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-2">
<Checkbox
id="bg-fill-toggle"
checked={getBackground().fillColor !== null}
onCheckedChange={(v) => onFillToggle(!!v)}
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-fade" class="text-xs font-medium">Fade mask</Label>
<span class="font-mono text-xs text-muted-foreground">
{getBackground().fade === 0 ? 'Off' : `${Math.round(getBackground().fade * 100)}%`}
</span>
</div>
<Slider
id="bg-fade"
type="single"
min={0}
max={1}
step={0.01}
value={getBackground().fade}
onValueChange={setBackgroundFade}
/>
<Label for="bg-fill-toggle" class="text-xs font-medium">Custom background fill</Label>
<p class="text-[11px] text-muted-foreground">
Fades the pattern out toward the edges, like a vignette.
</p>
</div>
<div class="flex items-center gap-2">
<input
id="bg-fill-color"
type="color"
class="size-7 cursor-pointer rounded-md border border-border bg-transparent p-0.5 disabled:cursor-not-allowed disabled:opacity-40"
value={getBackground().fillColor ?? fillDraft}
disabled={getBackground().fillColor === null}
oninput={onFillColorInput}
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-scale" class="text-xs font-medium">Size</Label>
<span class="font-mono text-xs text-muted-foreground"
>{Math.round(getBackground().scale * 100)}%</span
>
</div>
<Slider
id="bg-scale"
type="single"
min={0.4}
max={3}
step={0.05}
value={getBackground().scale}
onValueChange={setBackgroundScale}
disabled={noPattern}
/>
<span class="font-mono text-xs text-muted-foreground">{getBackground().fillColor ?? 'Auto'}</span>
</div>
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-opacity" class="text-xs font-medium">Opacity</Label>
<span class="font-mono text-xs text-muted-foreground">{Math.round(getBackground().opacity * 100)}%</span>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-rotation" class="text-xs font-medium">Rotation</Label>
<span class="font-mono text-xs text-muted-foreground"
>{Math.round(getBackground().rotation)}°</span
>
</div>
<Slider
id="bg-rotation"
type="single"
min={0}
max={359}
step={1}
value={getBackground().rotation}
onValueChange={setBackgroundRotation}
disabled={noPattern}
/>
</div>
<Slider
id="bg-opacity"
type="single"
min={0}
max={1}
step={0.01}
value={getBackground().opacity}
onValueChange={setBackgroundOpacity}
/>
</div>
{/if}
</div>
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-fade" class="text-xs font-medium">Fade mask</Label>
<span class="font-mono text-xs text-muted-foreground">
{getBackground().fade === 0 ? 'Off' : `${Math.round(getBackground().fade * 100)}%`}
</span>
</div>
<Slider id="bg-fade" type="single" min={0} max={1} step={0.01} value={getBackground().fade} onValueChange={setBackgroundFade} />
<p class="text-[11px] text-muted-foreground">Fades the pattern out toward the edges, like a vignette.</p>
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-scale" class="text-xs font-medium">Size</Label>
<span class="font-mono text-xs text-muted-foreground">{Math.round(getBackground().scale * 100)}%</span>
</div>
<Slider
id="bg-scale"
type="single"
min={0.4}
max={3}
step={0.05}
value={getBackground().scale}
onValueChange={setBackgroundScale}
disabled={noPattern}
/>
</div>
<div class="flex flex-col gap-1.5">
<div class="flex items-center justify-between">
<Label for="bg-rotation" class="text-xs font-medium">Rotation</Label>
<span class="font-mono text-xs text-muted-foreground">{Math.round(getBackground().rotation)}°</span>
</div>
<Slider
id="bg-rotation"
type="single"
min={0}
max={359}
step={1}
value={getBackground().rotation}
onValueChange={setBackgroundRotation}
disabled={noPattern}
/>
</div>
</div>
{/if}
<div class="shrink-0 border-t px-3 py-1.5 text-right text-[11px] text-muted-foreground">
Oikos {VERSION}
</div>
</div>
<div class="shrink-0 border-t px-3 py-1.5 text-right text-[11px] text-muted-foreground">Oikos {VERSION}</div>
</div>