fix(ui): implement UI review findings — a11y, IA, and consistency fixes

Fixes the reviewed gaps: keyboard-inaccessible delete controls (SessionRail,
Entities row), case-sensitive entity filter, two competing entity-detail
navigation patterns (standardize on EntitySheet), non-clickable Overview KPI
cards, a bare button bypassing the shared Button component, inconsistent
blur-only vs live filtering, and an unenforced sanitization assumption on
search snippet HTML (now using the already-present dompurify dependency).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 21:52:59 +02:00
parent b72267bd72
commit fb4c76ba82
12 changed files with 404 additions and 88 deletions

View File

@@ -1,10 +1,12 @@
<script lang="ts">
import DOMPurify from 'dompurify'
import { searchKnowledge, fetchRecentKnowledge, type KnowledgeHit, type RecentKnowledge, type KnowledgeItem } from '$lib/api'
import * as Card from '$lib/components/ui/card'
import { Badge } from '$lib/components/ui/badge'
import { Input } from '$lib/components/ui/input'
import { Button } from '$lib/components/ui/button'
import { ScrollArea } from '$lib/components/ui/scroll-area'
import EntitySheet from '$lib/components/EntitySheet.svelte'
import SearchIcon from '@lucide/svelte/icons/search'
import SparklesIcon from '@lucide/svelte/icons/sparkles'
import BotIcon from '@lucide/svelte/icons/bot'
@@ -54,8 +56,12 @@
return `${Math.floor(s / 86400)}d ago`
}
let sheetOpen = $state(false)
let selectedSlug = $state<string | null>(null)
function openEntity(slug: string) {
location.hash = '#/entity/' + encodeURIComponent(slug)
selectedSlug = slug
sheetOpen = true
}
</script>
@@ -117,8 +123,10 @@
<Badge variant={typeVariant(hit.type)}>{hit.type}</Badge>
</div>
{#if hit.snippet}
<!-- eslint-disable-next-line svelte/no-at-html-tags — server-sanitized ts_headline -->
<Card.Description class="text-xs">{@html hit.snippet}</Card.Description>
<!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized below, ts_headline only ever emits <b> -->
<Card.Description class="text-xs"
>{@html DOMPurify.sanitize(hit.snippet, { ALLOWED_TAGS: ['b'], ALLOWED_ATTR: [] })}</Card.Description
>
{/if}
{#if hit.linked_entities?.length}
<div class="mt-1 flex flex-wrap gap-1">
@@ -174,3 +182,5 @@
</ScrollArea>
{/if}
</div>
<EntitySheet slug={selectedSlug} bind:open={sheetOpen} />