chore(frontend): add ESLint + Prettier; fix real findings; format the tree

The frontend had no JS/TS linter — only svelte-check. Add flat-config ESLint (typescript-eslint
+ eslint-plugin-svelte) and Prettier (tabs/single-quote, matching the existing style).

Rules encode "catch bugs, not enforce taste":
  - svelte/require-each-key KEPT — it is the exact bug class as the feed mis-tap fix. Fixed every
    flagged block: keyed activeFilters, filteredUsers, stagedFiles (by previewUrl), captionTags,
    admin tabs/jobs/users, the export-viewer suggestions/filters/comments, and the static skeleton
    loops.
  - svelte/prefer-svelte-reactivity KEPT — inline-disabled only the verified-safe sites (a local
    freq Map in a $derived.by, throwaway URLSearchParams query builders), with a reason each.
  - svelte/no-navigation-without-resolve OFF — wants resolve() around every goto()/href; taste, not
    a bug, and pure churn.
  - svelte/no-unused-svelte-ignore OFF — those comments are consumed by svelte-check, which ESLint
    can't see, so it wrongly calls them unused; removing them would reintroduce a11y warnings.
  - no-explicit-any OFF for *.test.ts only (partial fixtures legitimately use any).

Real code fixes beyond keys: removed a dead jobLabel(), an unused ViewerComment import and unused
catch binding, an unused scroll-lock arg, and replaced an empty interface with a type alias.

Then `prettier --write` (54 files). Formatting only. Verified: eslint clean, svelte-check 0 errors,
vitest 46 passed, vite build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-07-15 20:45:42 +02:00
parent 4d14df18d0
commit f8cba95e49
59 changed files with 4046 additions and 998 deletions

View File

@@ -67,10 +67,10 @@
recoveryLoading = true;
recoveryError = '';
try {
const res = await api.post<{ jwt: string; user_id: string }>(
'/recover',
{ display_name: takenName, pin: recoveryPin.trim() }
);
const res = await api.post<{ jwt: string; user_id: string }>('/recover', {
display_name: takenName,
pin: recoveryPin.trim()
});
setAuth(res.jwt, recoveryPin.trim(), res.user_id, takenName);
goto('/feed');
} catch (e) {
@@ -141,14 +141,17 @@
<div class="flex min-h-screen items-center justify-center bg-gray-50 px-4 dark:bg-gray-950">
<div class="w-full max-w-sm">
{#if nameTaken}
<!-- Name-taken state: sign in with PIN or choose a different name -->
<div class="mb-5 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800/60 dark:bg-amber-950/30">
<p class="font-semibold text-amber-900 dark:text-amber-200">{takenName}" ist bereits vergeben.</p>
<div
class="mb-5 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800/60 dark:bg-amber-950/30"
>
<p class="font-semibold text-amber-900 dark:text-amber-200">
{takenName}" ist bereits vergeben.
</p>
<p class="mt-1 text-sm text-amber-800 dark:text-amber-300/90">
Wähle einen anderen Namen, z.&nbsp;B. einen Spitznamen oder füge deinen Nachnamen hinzu
(„{takenName} M." oder „{takenName} aus Berlin").
Wähle einen anderen Namen, z.&nbsp;B. einen Spitznamen oder füge deinen Nachnamen hinzu („{takenName}
M." oder „{takenName} aus Berlin").
</p>
</div>
@@ -156,7 +159,12 @@
Falls du das bist, melde dich mit deinem PIN an:
</p>
<form onsubmit={(e) => { e.preventDefault(); handleInlineRecover(); }}>
<form
onsubmit={(e) => {
e.preventDefault();
handleInlineRecover();
}}
>
<input
type="text"
value={recoveryPin}
@@ -170,7 +178,9 @@
/>
{#if recoveryError}
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recovery-error">{recoveryError}</p>
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="recovery-error">
{recoveryError}
</p>
{/if}
<button
@@ -193,9 +203,11 @@
<!-- Forgot the PIN entirely — ask a host to reset it in-app. -->
{#if pinRequestSent}
<p class="mt-3 rounded-lg bg-green-50 px-4 py-3 text-center text-sm text-green-700 dark:bg-green-950/30 dark:text-green-300">
Anfrage gesendet. Bitte einen Host, deine PIN zurückzusetzen — danach kannst du dich
mit der neuen PIN anmelden.
<p
class="mt-3 rounded-lg bg-green-50 px-4 py-3 text-center text-sm text-green-700 dark:bg-green-950/30 dark:text-green-300"
>
Anfrage gesendet. Bitte einen Host, deine PIN zurückzusetzen — danach kannst du dich mit
der neuen PIN anmelden.
</p>
{:else}
<button
@@ -207,16 +219,29 @@
{pinRequestLoading ? 'Wird gesendet…' : 'PIN vergessen? Host um Zurücksetzen bitten'}
</button>
{/if}
{:else}
<!-- Normal join form -->
<h1 class="mb-2 text-center text-2xl font-bold text-gray-900 dark:text-gray-100">Willkommen!</h1>
<h1 class="mb-2 text-center text-2xl font-bold text-gray-900 dark:text-gray-100">
Willkommen!
</h1>
{#if eventName}
<p class="mb-1 text-center text-lg font-semibold text-blue-600 dark:text-blue-400" data-testid="join-event-name">{eventName}</p>
<p
class="mb-1 text-center text-lg font-semibold text-blue-600 dark:text-blue-400"
data-testid="join-event-name"
>
{eventName}
</p>
{/if}
<p class="mb-6 text-center text-gray-600 dark:text-gray-400">Gib deinen Namen ein, um dem Event beizutreten.</p>
<p class="mb-6 text-center text-gray-600 dark:text-gray-400">
Gib deinen Namen ein, um dem Event beizutreten.
</p>
<form onsubmit={(e) => { e.preventDefault(); handleJoin(); }}>
<form
onsubmit={(e) => {
e.preventDefault();
handleJoin();
}}
>
<input
type="text"
bind:value={displayName}
@@ -227,7 +252,9 @@
/>
{#if error}
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="join-error">{error}</p>
<p class="mb-3 text-sm text-red-600 dark:text-red-400" data-testid="join-error">
{error}
</p>
{/if}
<button
@@ -241,15 +268,21 @@
</form>
<p class="mt-4 text-center text-sm">
<a href="/recover" data-testid="link-to-recover" class="text-blue-600 hover:underline dark:text-blue-400">Ich habe bereits einen Account</a>
<a
href="/recover"
data-testid="link-to-recover"
class="text-blue-600 hover:underline dark:text-blue-400">Ich habe bereits einen Account</a
>
</p>
{/if}
</div>
</div>
{#if showPinModal}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4" data-testid="pin-modal">
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4"
data-testid="pin-modal"
>
<div
class="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg dark:bg-gray-900"
role="dialog"
@@ -257,13 +290,21 @@
aria-labelledby="pin-modal-title"
use:focusTrap={{ onclose: closePinModal }}
>
<h2 id="pin-modal-title" class="mb-2 text-xl font-bold text-gray-900 dark:text-gray-100">Dein Wiederherstellungs-PIN</h2>
<h2 id="pin-modal-title" class="mb-2 text-xl font-bold text-gray-900 dark:text-gray-100">
Dein Wiederherstellungs-PIN
</h2>
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
Merke dir diesen PIN! Du brauchst ihn, um dein Konto auf einem anderen Gerät wiederherzustellen.
Merke dir diesen PIN! Du brauchst ihn, um dein Konto auf einem anderen Gerät
wiederherzustellen.
</p>
<div class="mb-4 flex items-center justify-center gap-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<span class="text-4xl font-mono font-bold tracking-widest text-gray-900 dark:text-gray-100" data-testid="pin-display">{pin}</span>
<div
class="mb-4 flex items-center justify-center gap-3 rounded-lg bg-gray-100 p-4 dark:bg-gray-800"
>
<span
class="text-4xl font-mono font-bold tracking-widest text-gray-900 dark:text-gray-100"
data-testid="pin-display">{pin}</span
>
<button
onclick={copyPin}
data-testid="pin-copy"