fix(frontend): clear svelte-check a11y and reactivity warnings

- TagBadge: derive color/style so they track a changing tag prop.
- FilterBar: seed tokens from the value prop via untrack (the effect keeps
  it in sync), silencing state_referenced_locally.
- FileCard: give the interactive card role="button".
- admin user toggles: add aria-labels to the switch buttons.

svelte-check is now clean (0 errors, 0 warnings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 10:13:43 +03:00
parent ac27ea3176
commit 36546c46c9
4 changed files with 11 additions and 3 deletions
@@ -138,6 +138,8 @@
class:focused class:focused
use:lazyload use:lazyload
data-file-index={index} data-file-index={index}
role="button"
tabindex="-1"
onpointerdown={onPointerDown} onpointerdown={onPointerDown}
onpointermove={onPointerMoveInternal} onpointermove={onPointerMoveInternal}
onpointerup={() => { onpointerup={() => {
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { untrack } from 'svelte';
import type { Tag } from '$lib/api/types'; import type { Tag } from '$lib/api/types';
import { fetchAllTags } from '$lib/api/tags'; import { fetchAllTags } from '$lib/api/tags';
import { buildDslFilter, parseDslFilter, tokenLabel } from '$lib/utils/dsl'; import { buildDslFilter, parseDslFilter, tokenLabel } from '$lib/utils/dsl';
@@ -16,7 +17,9 @@
let tags = $state<Tag[]>([]); let tags = $state<Tag[]>([]);
let search = $state(''); let search = $state('');
let tokens = $state<string[]>(parseDslFilter(value)); // Seed from the prop once; the $effect below keeps it in sync afterwards, so
// read it untracked to avoid the state-referenced-locally warning.
let tokens = $state<string[]>(untrack(() => parseDslFilter(value)));
let tagNames = $derived( let tagNames = $derived(
new Map(tags.filter((t) => t.id && t.name).map((t) => [t.id as string, t.name as string])) new Map(tags.filter((t) => t.id && t.name).map((t) => [t.id as string, t.name as string]))
); );
@@ -13,8 +13,8 @@
let { tag, onclick, size = 'md', focused = false, index }: Props = $props(); let { tag, onclick, size = 'md', focused = false, index }: Props = $props();
const color = tag.color ?? tag.category_color; let color = $derived(tag.color ?? tag.category_color);
const style = color ? `background-color: #${color}` : ''; let style = $derived(color ? `background-color: #${color}` : '');
</script> </script>
{#if onclick} {#if onclick}
@@ -107,6 +107,7 @@
class:on={isAdmin} class:on={isAdmin}
role="switch" role="switch"
aria-checked={isAdmin} aria-checked={isAdmin}
aria-label="Admin"
onclick={() => (isAdmin = !isAdmin)}><span class="thumb"></span></button onclick={() => (isAdmin = !isAdmin)}><span class="thumb"></span></button
> >
</div> </div>
@@ -121,6 +122,7 @@
class:on={canCreate} class:on={canCreate}
role="switch" role="switch"
aria-checked={canCreate} aria-checked={canCreate}
aria-label="Can create"
onclick={() => (canCreate = !canCreate)}><span class="thumb"></span></button onclick={() => (canCreate = !canCreate)}><span class="thumb"></span></button
> >
</div> </div>
@@ -140,6 +142,7 @@
class:danger={isBlocked} class:danger={isBlocked}
role="switch" role="switch"
aria-checked={isBlocked} aria-checked={isBlocked}
aria-label="Blocked"
onclick={() => (isBlocked = !isBlocked)}><span class="thumb"></span></button onclick={() => (isBlocked = !isBlocked)}><span class="thumb"></span></button
> >
</div> </div>