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:
@@ -44,27 +44,29 @@
|
||||
<ScrollArea class="min-h-0 flex-1">
|
||||
<div class="flex flex-col gap-1 pr-2">
|
||||
{#each $sessions as session (session.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="group flex flex-col items-start gap-0.5 rounded-md border px-2 py-1.5 text-left text-xs transition-colors hover:bg-muted/60 {$currentSession === session.id ? 'border-primary bg-muted/50' : 'border-transparent'}"
|
||||
onclick={() => handleClick(session.id)}
|
||||
>
|
||||
<span class="flex w-full items-center justify-between gap-1">
|
||||
<span class="min-w-0 truncate font-medium">{session.title || 'Untitled'}</span>
|
||||
<span
|
||||
class="shrink-0 rounded p-0.5 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-destructive/20 hover:text-destructive"
|
||||
onclick={(e) => handleDelete(e, session.id)}
|
||||
title={confirmDelete === session.id ? 'Click again to confirm delete' : 'Delete session'}
|
||||
>
|
||||
{#if confirmDelete === session.id}
|
||||
<span class="text-[10px] font-semibold text-destructive">Sure?</span>
|
||||
{:else}
|
||||
<Trash2Icon class="size-3" />
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
<span class="text-[11px] text-muted-foreground">{relativeTime(session.last_active_at)}</span>
|
||||
</button>
|
||||
<div class="group relative">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full flex-col items-start gap-0.5 rounded-md border py-1.5 pl-2 pr-7 text-left text-xs transition-colors hover:bg-muted/60 {$currentSession === session.id ? 'border-primary bg-muted/50' : 'border-transparent'}"
|
||||
onclick={() => handleClick(session.id)}
|
||||
>
|
||||
<span class="min-w-0 max-w-full truncate font-medium">{session.title || 'Untitled'}</span>
|
||||
<span class="text-[11px] text-muted-foreground">{relativeTime(session.last_active_at)}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-1 top-1.5 shrink-0 rounded p-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 hover:bg-destructive/20 hover:text-destructive"
|
||||
onclick={(e) => handleDelete(e, session.id)}
|
||||
aria-label={confirmDelete === session.id ? 'Click again to confirm delete' : 'Delete session'}
|
||||
title={confirmDelete === session.id ? 'Click again to confirm delete' : 'Delete session'}
|
||||
>
|
||||
{#if confirmDelete === session.id}
|
||||
<span class="text-[10px] font-semibold text-destructive">Sure?</span>
|
||||
{:else}
|
||||
<Trash2Icon class="size-3" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="px-2 py-4 text-center text-xs text-muted-foreground">No sessions yet.</p>
|
||||
{/each}
|
||||
|
||||
@@ -21,6 +21,16 @@ export function relativeTime(iso: string | null | undefined): string {
|
||||
return `${d}d ago`;
|
||||
}
|
||||
|
||||
// debounce wraps fn so rapid calls (e.g. keystrokes in a filter input)
|
||||
// collapse into one invocation after `wait`ms of silence.
|
||||
export function debounce<T extends (...args: never[]) => void>(fn: T, wait = 300): T {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
return ((...args: Parameters<T>) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn(...args), wait);
|
||||
}) as T;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
Reference in New Issue
Block a user