fix(frontend): enforce lint locally and in CI, fix all pre-existing violations
Some checks failed
CI / Unit & Component Tests (push) Successful in 1m59s
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled

## Pre-commit hook
- Add .husky/pre-commit at repo root: runs `cd frontend && npm run lint`
- Update prepare script in package.json to auto-configure git hooks path
  on npm install (git -C .. config core.hooksPath .husky)
- Add lint step to CI unit-tests job so it catches issues before tests run
- Add generated dirs to .prettierignore (paraglide_bak*, test-results, .auth)
- Add src/lib/paraglide_bak* to .gitignore so ESLint can ignore them

## ESLint fixes (all pre-existing)
- Disable svelte/no-navigation-without-resolve: false positive in SvelteKit
  (rule targets Svelte 5 standalone routing, not SvelteKit <a href>)
- Fix svelte/require-each-key: add (item.id)/(item) keys to all {#each} blocks
  across 10 files — improves Svelte reconciliation performance
- Fix svelte/prefer-writable-derived in PersonTypeahead: $state+$effect → $derived
- Fix svelte/prefer-svelte-reactivity: URLSearchParams → SvelteURLSearchParams,
  Map → SvelteMap (enables Svelte reactive tracking)
- Fix @typescript-eslint/no-unused-vars: remove dead imports/variables

## Prettier
- Run npm run format to bring all source files in line with .prettierrc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-20 15:55:42 +01:00
parent 28dea45cc3
commit db2fc33e99
53 changed files with 2522 additions and 2061 deletions

View File

@@ -4,6 +4,7 @@ import { goto } from '$app/navigation';
import TagInput from '$lib/components/TagInput.svelte';
import { slide } from 'svelte/transition';
import { untrack } from 'svelte';
import { SvelteURLSearchParams } from 'svelte/reactivity';
import { m } from '$lib/paraglide/messages.js';
import { formatDate } from '$lib/utils/date';
@@ -28,7 +29,7 @@ const hasAdvancedFilters = (filters: typeof data.filters) =>
let showAdvanced = $state(untrack(() => hasAdvancedFilters(data.filters)));
function triggerSearch() {
const params = new URLSearchParams();
const params = new SvelteURLSearchParams();
if (q) params.set('q', q);
if (from) params.set('from', from);
@@ -88,7 +89,12 @@ $effect(() => {
class="block w-full border-gray-300 py-2.5 pr-10 pl-3 placeholder-gray-400 shadow-sm focus:border-brand-navy focus:ring-brand-navy"
/>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Mag-Glass-MD.svg" alt="" aria-hidden="true" class="h-4 w-4 opacity-40" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Mag-Glass-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4 opacity-40"
/>
</div>
</div>
@@ -97,7 +103,12 @@ $effect(() => {
onclick={() => (showAdvanced = !showAdvanced)}
class="flex items-center gap-2 border border-gray-300 bg-gray-50 px-4 py-2.5 text-sm font-bold tracking-wide text-gray-600 uppercase transition hover:bg-gray-100 hover:text-brand-navy"
>
<img src="/degruyter-icons/Simple/Small-16px/SVG/Action/Chevron/Chevron-Down-SM.svg" alt="" aria-hidden="true" class="h-4 w-4 transform transition-transform duration-200 {showAdvanced ? 'rotate-180' : ''}" />
<img
src="/degruyter-icons/Simple/Small-16px/SVG/Action/Chevron/Chevron-Down-SM.svg"
alt=""
aria-hidden="true"
class="h-4 w-4 transform transition-transform duration-200 {showAdvanced ? 'rotate-180' : ''}"
/>
{m.docs_btn_filter()}
</button>
@@ -107,7 +118,12 @@ $effect(() => {
class="flex items-center justify-center border border-transparent px-3 py-2.5 text-gray-400 transition hover:text-red-500"
title={m.docs_btn_reset_title()}
>
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Close-MD.svg" alt="" aria-hidden="true" class="h-5 w-5 opacity-40" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Close-MD.svg"
alt=""
aria-hidden="true"
class="h-5 w-5 opacity-40"
/>
</a>
</div>
@@ -193,13 +209,18 @@ $effect(() => {
<!-- DOCUMENT LIST HEADER -->
<div class="mb-2 flex justify-end">
{#if data.canWrite}
<a
href="/documents/new"
class="inline-flex items-center gap-1 text-sm font-medium text-brand-navy/60 transition-colors hover:text-brand-navy"
>
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Add/Add-General-MD.svg" alt="" aria-hidden="true" class="h-4 w-4" />
{m.docs_btn_new()}
</a>
<a
href="/documents/new"
class="inline-flex items-center gap-1 text-sm font-medium text-brand-navy/60 transition-colors hover:text-brand-navy"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Add/Add-General-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4"
/>
{m.docs_btn_new()}
</a>
{/if}
</div>
@@ -211,7 +232,7 @@ $effect(() => {
</div>
{:else if data.documents && data.documents.length > 0}
<ul class="divide-y divide-gray-100">
{#each data.documents as doc}
{#each data.documents as doc (doc.id)}
<li class="group transition-colors duration-200 hover:bg-brand-sand/10">
<!-- LINK TO DETAIL PAGE -->
<a href="/documents/{doc.id}" class="block p-6">
@@ -240,12 +261,22 @@ $effect(() => {
<!-- Metadata Row -->
<div class="mb-4 flex flex-wrap gap-6 font-sans text-sm text-gray-500">
<div class="flex items-center">
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Calendar/Calendar-Add-MD.svg" alt="" aria-hidden="true" class="mr-1.5 h-4 w-4" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Calendar/Calendar-Add-MD.svg"
alt=""
aria-hidden="true"
class="mr-1.5 h-4 w-4"
/>
{doc.documentDate ? formatDate(doc.documentDate) : '—'}
</div>
{#if doc.location}
<div class="flex items-center">
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Location-MD.svg" alt="" aria-hidden="true" class="mr-1.5 h-4 w-4" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Location-MD.svg"
alt=""
aria-hidden="true"
class="mr-1.5 h-4 w-4"
/>
{doc.location}
</div>
{/if}
@@ -284,7 +315,7 @@ $effect(() => {
<!-- Tags Display -->
{#if doc.tags && doc.tags.length > 0}
<div class="mt-4 flex flex-wrap gap-2 pt-3">
{#each doc.tags as tag}
{#each doc.tags as tag (tag.id)}
<button
type="button"
class="relative z-10 inline-flex cursor-pointer items-center rounded bg-brand-sand/30 px-2 py-1 text-[10px] font-bold tracking-widest text-brand-navy uppercase transition-colors hover:bg-brand-navy hover:text-white"
@@ -301,7 +332,12 @@ $effect(() => {
<div
class="hidden items-center text-gray-300 transition-colors group-hover:text-brand-mint sm:flex"
>
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Right-MD.svg" alt="" aria-hidden="true" class="h-6 w-6" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Right-MD.svg"
alt=""
aria-hidden="true"
class="h-6 w-6"
/>
</div>
</div>
</a>
@@ -314,7 +350,12 @@ $effect(() => {
<div
class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-brand-sand/30"
>
<img src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Mag-Glass-MD.svg" alt="" aria-hidden="true" class="h-6 w-6" />
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Mag-Glass-MD.svg"
alt=""
aria-hidden="true"
class="h-6 w-6"
/>
</div>
<h3 class="font-serif text-lg font-medium text-brand-navy">{m.docs_empty_heading()}</h3>
<p class="mt-1 font-sans text-sm text-gray-500">