fix(web): replace generic spinners with content-shaped loading skeletons
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

The six loading states across the Knowledge wiki (initial app load, the
reader's note/history fetches, and the four Cleanup tabs) all showed a
centered spinner with no relation to what was about to render — costing
a full reflow the instant real content landed. Replaces each with a
skeleton shaped like its actual content (tree rows, reader header +
prose, revision list + diff, cluster cards, table rows, flat lists)
using the existing shadcn Skeleton primitive already used elsewhere.

Verified each of the six by temporarily injecting a delay into
fetchWithAuth and screenshotting the transient state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 08:44:52 +02:00
parent 89312a9ce4
commit 7e1ccad5f4
3 changed files with 151 additions and 12 deletions

View File

@@ -20,6 +20,7 @@
import WikiQuickOpen from '$lib/components/knowledge/WikiQuickOpen.svelte'
import * as Dialog from '$lib/components/ui/dialog'
import { Button } from '$lib/components/ui/button'
import { Skeleton } from '$lib/components/ui/skeleton'
import WrenchIcon from '@lucide/svelte/icons/wrench'
import BookOpenIcon from '@lucide/svelte/icons/book-open'
@@ -149,9 +150,51 @@
<div class="min-h-0 flex-1">
{#if itemsLoading}
<div class="flex h-full items-center justify-center text-sm text-muted-foreground">
Loading…
</div>
<!-- Shaped like the real three-pane layout below (same Splitpanes
proportions) rather than a centered spinner: a spinner tells the
operator nothing about what's about to render and costs a full
reflow the instant real content lands. This costs none — the
panes are already the right size. -->
<Splitpanes theme="oikos-theme" dblClickSplitter={false} class="h-full">
<Pane size={22} minSize={15} maxSize={40}>
<div class="flex h-full flex-col gap-3 overflow-hidden p-1.5">
<Skeleton class="h-7 w-full" />
<Skeleton class="h-3 w-16" />
<div class="flex flex-col gap-3">
{#each Array(4) as _, gi (gi)}
<div class="flex flex-col gap-1.5">
<Skeleton class="h-3 w-20" />
<div class="ml-3 flex flex-col gap-2 border-l pl-2">
{#each Array(3) as _, ri (ri)}
<Skeleton class="h-3.5" style="width: {75 - ri * 15}%" />
{/each}
</div>
</div>
{/each}
</div>
</div>
</Pane>
<Pane size={56} minSize={30}>
<div class="flex h-full flex-col gap-3 overflow-hidden p-2">
<Skeleton class="h-4 w-32" />
<Skeleton class="h-9 w-40" />
<div class="mt-1 grid grid-cols-4 gap-px overflow-hidden rounded-lg bg-border/60">
{#each Array(4) as _, i (i)}
<div class="flex flex-col gap-1.5 bg-card px-3 py-2.5">
<Skeleton class="h-3 w-14" />
<Skeleton class="h-5 w-8" />
</div>
{/each}
</div>
<Skeleton class="mt-2 h-3 w-full" />
<div class="mt-2 flex flex-col gap-2.5">
{#each Array(5) as _, i (i)}
<Skeleton class="h-4" style="width: {85 - i * 8}%" />
{/each}
</div>
</div>
</Pane>
</Splitpanes>
{:else if loadError}
<div class="flex h-full flex-col items-center justify-center gap-2 text-sm">
<p class="text-destructive">{loadError}</p>