audit(frontend): score frontend/ against legibility rubric C1–C10 #388

Closed
opened 2026-05-04 16:03:30 +02:00 by marcel · 1 comment
Owner

Context

Part of Epic #387 — Codebase Legibility Audit. This is AUDIT-1: read-only assessment of frontend/ against the full Legibility Rubric. The output is a Markdown report under docs/audits/audit-frontend-report.md following the per-subsystem orient template. No code changes in this issue.

Inputs (read first)

See the first comment of #387 for the complete reference bundle:

  • Project Brief (PROJ-LEGIBILITY-001) with O1–O4 measurable goals
  • Personas (Anja, Tobias, Successor-X)
  • Canonical Domain Set + boundary decisions D-OQ-1..8 / D-FE-1..4
  • Full Legibility Rubric (C1–C10, ~50 checks)
  • Per-Subsystem Orient Template (10 sections)

Scope (in)

  • Everything under frontend/src/ — routes, components, lib, hooks, utils, stores, server, paraglide
  • frontend/messages/ (i18n source)
  • frontend/package.json, build scripts, vitest/playwright config
  • frontend/CLAUDE.md (evaluate as documentation surface)
  • frontend/Dockerfile (only the developer-experience aspects)

Scope (out)

  • frontend/node_modules/
  • frontend/.svelte-kit/, frontend/.svelte-kit.old/
  • frontend/test-results.locked/, frontend/e2e/.auth/
  • Generated code under frontend/src/lib/paraglide/
  • Generated TypeScript API types (note their existence in §8 but don't audit their contents)

Rubric checks particularly relevant to this subsystem

All of C1, C2 (frontend portion), C3.5, C3.6, C4, C5, C6.3, C6.4, C7, C8.2, C8.3, C8.4, C9.2, C10. C1–C2 may overlap with AUDIT-5 — note overlap and let AUDIT-6 deduplicate at rollup.

Acceptance criteria

  • File docs/audits/audit-frontend-report.md exists on a feature branch
  • Report contains all 10 template sections, none empty (or "N/A — reason")
  • §3 scorecard covers every rubric check applicable to a SvelteKit frontend
  • §4 assigns the subsystem an overall 🟢 / 🟡 / 🔴 verdict per the threshold rules
  • §5 lists every component currently in lib/components/ (root level) that doesn't map cleanly to the canonical domain set, with a recommended target location
  • §6 lists every candidate for lib/shared/ with admission-criteria justification
  • §7 catalogs dead code, including the frontend/.svelte-kit.old/ and frontend/test-results.locked/ directories
  • §10 lists top-5 recommendations in priority order, each ≤2 sentences
  • No prose contains "ask Marcel," "Claude generated," "TODO," or "non-obvious" without explanation
  • PR opened that adds the report under docs/audits/ and is merged before this issue is closed

Definition of Done

Report committed under docs/audits/audit-frontend-report.md on main. Top-5 recommendations summarized as a closing comment on this issue. Issue closed via Closes #N in the merge commit.

Dispatch

Recommended: hand to a parallel Explore agent with the prompt:

Read issue #387 first comment for the full reference bundle. Then read this issue (#TBD). Then audit frontend/ per the orient template and produce docs/audits/audit-frontend-report.md. Read-only; no code changes.

## Context Part of **Epic #387** — Codebase Legibility Audit. This is **AUDIT-1**: read-only assessment of `frontend/` against the full Legibility Rubric. The output is a Markdown report under `docs/audits/audit-frontend-report.md` following the per-subsystem orient template. **No code changes in this issue.** ## Inputs (read first) See **the first comment of #387** for the complete reference bundle: - Project Brief (PROJ-LEGIBILITY-001) with O1–O4 measurable goals - Personas (Anja, Tobias, Successor-X) - Canonical Domain Set + boundary decisions D-OQ-1..8 / D-FE-1..4 - Full Legibility Rubric (C1–C10, ~50 checks) - Per-Subsystem Orient Template (10 sections) ## Scope (in) - Everything under `frontend/src/` — routes, components, lib, hooks, utils, stores, server, paraglide - `frontend/messages/` (i18n source) - `frontend/package.json`, build scripts, vitest/playwright config - `frontend/CLAUDE.md` (evaluate as documentation surface) - `frontend/Dockerfile` (only the developer-experience aspects) ## Scope (out) - `frontend/node_modules/` - `frontend/.svelte-kit/`, `frontend/.svelte-kit.old/` - `frontend/test-results.locked/`, `frontend/e2e/.auth/` - Generated code under `frontend/src/lib/paraglide/` - Generated TypeScript API types (note their existence in §8 but don't audit their contents) ## Rubric checks particularly relevant to this subsystem All of C1, C2 (frontend portion), C3.5, C3.6, C4, C5, C6.3, C6.4, C7, C8.2, C8.3, C8.4, C9.2, C10. C1–C2 may overlap with AUDIT-5 — note overlap and let AUDIT-6 deduplicate at rollup. ## Acceptance criteria - [ ] File `docs/audits/audit-frontend-report.md` exists on a feature branch - [ ] Report contains all 10 template sections, none empty (or "N/A — reason") - [ ] §3 scorecard covers every rubric check applicable to a SvelteKit frontend - [ ] §4 assigns the subsystem an overall 🟢 / 🟡 / 🔴 verdict per the threshold rules - [ ] §5 lists every component currently in `lib/components/` (root level) that doesn't map cleanly to the canonical domain set, with a recommended target location - [ ] §6 lists every candidate for `lib/shared/` with admission-criteria justification - [ ] §7 catalogs dead code, including the `frontend/.svelte-kit.old/` and `frontend/test-results.locked/` directories - [ ] §10 lists top-5 recommendations in priority order, each ≤2 sentences - [ ] No prose contains "ask Marcel," "Claude generated," "TODO," or "non-obvious" without explanation - [ ] PR opened that adds the report under `docs/audits/` and is merged before this issue is closed ## Definition of Done Report committed under `docs/audits/audit-frontend-report.md` on `main`. Top-5 recommendations summarized as a closing comment on this issue. Issue closed via `Closes #N` in the merge commit. ## Dispatch Recommended: hand to a parallel Explore agent with the prompt: > Read issue #387 first comment for the full reference bundle. Then read this issue (#TBD). Then audit `frontend/` per the orient template and produce `docs/audits/audit-frontend-report.md`. Read-only; no code changes.
marcel added this to the (deleted) milestone 2026-05-04 16:03:30 +02:00
marcel added the P1-highauditlegibility labels 2026-05-04 16:05:35 +02:00
Author
Owner

AUDIT-1 — Frontend Legibility Audit

Read-only assessment of frontend/ against RUBRIC-LEGIBILITY-001. Report follows TEMPLATE-ORIENT-001. Reference bundle: issue #387, first comment.


1. Subsystem profile

The frontend is a SvelteKit 2 + Svelte 5 (runes) SSR app served by @sveltejs/adapter-node. It owns the entire web UI for the Familienarchiv: file-based routing under src/routes/, a typed API client (openapi-fetch) generated from the backend's OpenAPI spec into src/lib/generated/api.ts, Tailwind 4 styling with brand utilities defined in src/routes/layout.css, Paraglide i18n (de/en/es) compiled into src/lib/paraglide/, Vitest for unit + browser-mode component tests, and Playwright for E2E. Auth is cookie-based; SSR hooks in src/hooks.server.ts inject Authorization from auth_token and gate non-public routes.

frontend/
├── src/
│   ├── routes/                # 43 +page.svelte, 35 +page.server.ts, 10 +layout files; 39 page-local *.svelte sub-components
│   ├── lib/
│   │   ├── components/        # 79 flat components + 3 sub-dirs (document/, chronik/, user/) — primary AI-smell hotspot
│   │   ├── generated/         # api.ts (do not edit)
│   │   ├── stores/ services/ hooks/ utils/ actions/ ocr/ types/ server/ assets/
│   │   ├── paraglide/         # generated i18n
│   │   ├── paraglide_bak{,2,3,4,_b1_chown_pending,_now}/  # 6 stale dirs (~4 MB)
│   │   ├── api.server.ts errors.ts types.ts utils.ts relativeTime.ts search.ts person-validation.ts relationshipLabels.ts
│   ├── hooks.server.ts hooks.ts app.d.ts app.html
├── e2e/                       # 33 Playwright specs + auth.setup.ts
├── messages/                  # de.json en.json es.json (1049 lines each, in sync)
├── README.md                  # ~40-line stub from `npx sv create` — does not describe Familienarchiv
├── CLAUDE.md                  # 198 lines, currently the de-facto frontend doc
├── .svelte-kit.old/  .svelte-kit-backup/  test-results.locked/  proofshot-artifacts/  # stale top-level dirs

2. Inventory

Routes (top-level)

Route Domain Notes
/ dashboard Home dashboard; imports 5 dashboard widgets
/documents /documents/[id] /documents/[id]/edit /documents/new /documents/bulk-edit document Full CRUD
/persons /persons/[id] /persons/[id]/edit /persons/new person CRUD + merge
/stammbaum person.relationship Tree view
/briefwechsel conversation (derived) Bilateral timeline; route name is German
/aktivitaeten activity (derived) Chronik / activity feed
/geschichten /geschichten/[id] /geschichten/[id]/edit /geschichten/new geschichte CRUD
/enrich /enrich/[id] /enrich/done document (sub-flow) Bulk metadata enrichment
/admin /admin/groups /admin/users /admin/tags /admin/ocr /admin/invites /admin/system user / tag / ocr (admin views) Six sub-areas
/users/[id] user Public profile view — overlaps semantically with /admin/users/[id]
/profile user Self-service
/login /logout /register /forgot-password /reset-password security (cross-cutting) Auth flows
/hilfe /hilfe/transkription shared/help Static help pages
/api/* /api/[...path] /api/documents /api/persons /api/tags shared (server proxy) SSR-only endpoints
/demo /demo/paraglide Scaffold leftover from sv create

lib/ root files

File Purpose Apparent home
api.server.ts Typed OpenAPI client factory shared/ (framework infra)
errors.ts ErrorCode mirror + paraglide lookup shared/error-handling
types.ts Comment / Mention / TranscriptionBlockData / Annotation types document (mostly), person (Mention) — currently mixed
utils.ts Re-exports isoToGerman from utils/date (60-byte stub) shared/ — orphan vestige
relativeTime.ts "vor 3 Tagen" formatter shared/i18n
search.ts URL search-param helpers shared/
person-validation.ts Person form validation person/
relationshipLabels.ts Relationship enum → German label person.relationship
index.ts Empty placeholder — // place files you want to import … dead

Components currently in lib/components/ (root level — 79 files)

The flat dir mixes ≥9 domains and 3 cross-cutting categories. Domain assignment per the canonical set:

Component Domain
BackButton, ConfirmDialog, DateInput, LanguageSwitcher, Pagination, SortDropdown, ThemeToggle, OverflowPillButton, OverflowPillDisplay, ProgressRing, ExpandableText, GroupDivider, HelpPopover, UnsavedWarningBanner, UploadSuccessBanner, DistributionBar shared/ui
MentionEditor, MentionDropdown, PersonMentionEditor, PersonMentionEditor.test-host, CommentMessage, CommentThread shared/discussion (per D-FE-3)
PersonChip, PersonChipRow, PersonHoverCard, PersonMultiSelect, PersonTypeahead, PersonTypeBadge, PersonTypeSelector, RelationshipChip, RelationshipPill, AddRelationshipForm, StammbaumCard, StammbaumSidePanel, StammbaumTree, ContributorStack person (D-FE-1; relationship is sub-package)
TagChipList, TagInput, TagParentPicker tag (D-FE-2)
DocumentRow, DocumentThumbnail, DocumentTopBar, DocumentViewer, DocumentMetadataDrawer, DocumentMultiSelect, DocumentStatusChip, ConversationThumbnail, ThumbnailRow, ReadyColumn, SegmentationColumn, TranscriptionColumn, EnrichmentBlock, PdfViewer, PdfControls, FileSwitcherStrip (in document/) document
AnnotationLayer, AnnotationShape, AnnotationEditOverlay document.annotation
TranscriptionBlock, TranscriptionBlock.test-host, TranscriptionEditView, TranscriptionReadView, TranscriptionPanelHeader, TranscribeCoachEmptyState, TranscribeDragDemo, RichtlinienRuleCard, ScriptTypeSelect document.transcription
OcrTrigger, OcrProgress, OcrTrainingCard, SegmentationTrainingCard, TrainingHistory ocr
NotificationBell, NotificationDropdown notification
GeschichteEditor, GeschichtenCard geschichte
MissionControlStrip, DashboardActivityFeed, DashboardFamilyPulse, DashboardNeedsMetadata, DashboardRecentDocuments, DashboardResumeStrip shared/dashboard (D-FE-4)

Sub-directories:

Dir Members Domain
lib/components/document/ 14 files (BulkDocumentEditLayout, BulkDropZone, BulkSelectionBar, DescriptionSection, DocumentEditLayout, FieldLabelBadge, FileSwitcherStrip, ScopeCard, TranscriptionSection, UploadSaveBar, UploadZone, WhoWhenSection) document
lib/components/chronik/ ChronikEmptyState, ChronikErrorCard, ChronikFilterPills, ChronikFuerDichBox, ChronikRow, ChronikTimeline activity (derived)
lib/components/user/ UserGroupsSection, UserPasswordSection, UserProfileSection (3 files, no tests) user

Stores, hooks, utils, services, actions

Path Files Domain
lib/stores/ bulkSelection.svelte.ts (document), notifications.svelte.ts (notification) document + notification
lib/hooks/ useBlockAutoSave, useBlockDragDrop, useFileLoader, usePdfRenderer, useTypeahead, useUnsavedWarning mostly document.transcription + shared
lib/services/ confirm.svelte.ts + test-host shared/ui
lib/actions/ clickOutside.ts, radioGroupNav.ts shared/ui
lib/utils/ 24 ts files: date, debounce, sort, sanitize, notifications, requiredFields, validateFile, filename, time, documentStatusLabel, personFormat, personLifeDates, groupDocuments, comment, commentDeepLink, mention, mentionSerializer, transcriptionMarkers, extractText, blockConflictMerge, saveBlockWithConflictRetry, deepLinkScroll, hoverCardPosition, date-buckets mixed (≥7 domains share this single bucket)
lib/types/ personHoverCard.ts, training.ts person, ocr
lib/ocr/ translateOcrProgress.ts ocr
lib/server/ locale.ts shared/i18n

Page-local components co-located in src/routes/ (≥39 files)

  • routes/AppNav.svelte, AuthHeader.svelte, UserMenu.svelte, DocumentList.svelte, DropZone.svelte, SearchFilterBar.svelte — shell components living next to the layout
  • routes/admin/{EntityNav,EntityNavSection}.svelte, routes/admin/{groups,tags,users,ocr}/… — many *ListPanel.svelte, Tag*.svelte, OcrHealthBar.svelte, etc.
  • routes/briefwechsel/{ConversationFilterBar,ConversationTimeline,Correspondent…,Correspondenz…,SinglePersonHintBar}.svelte (7 files)
  • routes/persons/[id]/{PersonCard,PersonDocumentList,PersonRelationshipsCard,PersonMergePanel,CoCorrespondentsList,NameHistoryCard}.svelte + edit subfolder
  • routes/profile/{PersonalInfoForm,PasswordChangeForm}.svelte

This co-location is a second, parallel organizational paradigm to lib/components/ — the codebase has not chosen one. (See §5.)


3. Rubric scorecard

Check Result Severity Evidence Recommendation
C1.1 Root README explains purpose ≤3 sentences FAIL Critical No README.md at repo root; frontend/README.md:1-39 is the unmodified sv create template (# sv … "If you're seeing this, you've probably already done this step. Congrats!") Write README.md at repo root with one-paragraph product description; replace frontend/README.md stub (or rely on frontend/CLAUDE.md content moved to README). Overlap with AUDIT-5; flagging here per scope.
C1.2 README lists 5 components FAIL Major frontend/README.md:1-39 has none Add component map (frontend/backend/OCR/DB/infra) to root README.
C1.3 docs/ARCHITECTURE.md with diagram FAIL Major No docs/ARCHITECTURE.md; docs/architecture/c4-diagrams.md exists but is not linked from any README Create or link the C4 diagrams from a top-level ARCHITECTURE.md.
C1.4 README links to run / contribute / issues FAIL Major frontend/README.md has none of these; COLLABORATING.md and docs/ exist but are not linked Cross-link from frontend/README.md.
C1.5 Repo root free of stray/old folders FAIL Minor frontend/.svelte-kit.old/, .svelte-kit-backup/, test-results.locked/, proofshot-artifacts/ (committed-or-tracked); 6 × src/lib/paraglide_bak*/ (~4 MB) Delete or .gitignore properly.
C2.1 Single command brings stack up PARTIAL/FAIL Major frontend/README.md:18-26 says npm run dev only; no instructions to run backend/MinIO/DB; frontend/CLAUDE.md:142-150 does point to root docker-compose up -d Move CLAUDE.md run instructions into README.
C2.2 Prerequisites with versions PARTIAL Major frontend/README.md has none; frontend/CLAUDE.md:7-19 lists Svelte 5, TS 5.9, Vite 7 (no Node version) Add Node version (≥24 per package.json @types/node) to README.
C2.3 Default credentials documented UNKNOWN Major Neither frontend/README.md nor frontend/CLAUDE.md mentions admin@familyarchive.local / admin123; defer to AUDIT-5 Document in root README.
C2.4 Common failure modes FAIL Minor Only frontend/e2e/CLAUDE.md:130-138 has a small troubleshooting table; no frontend-wide section Add to frontend README.
C2.5 Per-subsystem README PARTIAL Major frontend/README.md exists but is the empty sv stub; frontend/e2e/CLAUDE.md is good but is CLAUDE-marked, not human-marked Promote frontend/CLAUDE.md content into frontend/README.md.
C3.1 Domains stated in docs PARTIAL Critical frontend/CLAUDE.md:38-56 lists routes but does not name domains as bounded contexts Add a "Domains" section (the canonical 7+2 list from #387).
C3.2 One-paragraph definitions per domain FAIL Major None present Per-domain README under lib/<domain>/README.md (see §8).
C3.3 Glossary disambiguates Person ≠ AppUser FAIL Critical No glossary file in frontend/; tension is real (/users/[id] is a Person profile and /admin/users/[id] is an AppUser) Add docs/GLOSSARY.md and link from frontend README. Critical for Anja & Tobias.
C3.4 Backend domain-based packages N/A Backend audit (AUDIT-2)
C3.5 Frontend lib/ domain-based, names stack-symmetric FAIL Critical lib/components/ is a flat 79-file bucket spanning ≥9 domains; only document/, chronik/, user/ sub-dirs exist; lib/utils/ mixes mention, transcription, document, person, file, sort, dates, etc. (24 files in one folder) This is the central refactor target — REFACTOR-2 (#408).
C3.6 shared/ justified N/A — shared/ does not exist yet Major No lib/shared/ exists; cross-cutting code is currently in lib/ root or scattered Create lib/shared/{ui,discussion,dashboard,error-handling,i18n} per D-FE-3/4.
C4.1 Locate every file for a domain ≤2 min FAIL Critical Person-touching code is in: lib/components/Person*.svelte (7 files), lib/components/Stammbaum*.svelte (3 files), lib/components/AddRelationshipForm.svelte, lib/utils/personFormat.ts, lib/utils/personLifeDates.ts, lib/person-validation.ts, lib/relationshipLabels.ts, lib/types/personHoverCard.ts, routes/persons/, routes/persons/[id]/Person*.svelte (page-local) — minimum 6 different parents to scan Consolidate under lib/person/ per REFACTOR-2.
C4.2 Locate controller+service+page+component for a feature ≤5 min PARTIAL Major Front-end-only legibility for a feature like "transcription" is hard: TranscriptionBlock.svelte, TranscriptionEditView.svelte, TranscriptionReadView.svelte, TranscriptionPanelHeader.svelte, TranscriptionColumn.svelte, TranscribeCoachEmptyState.svelte, TranscribeDragDemo.svelte, RichtlinienRuleCard.svelte, ScriptTypeSelect.svelte, lib/utils/transcriptionMarkers.ts, lib/utils/blockConflictMerge.ts, lib/utils/saveBlockWithConflictRetry.ts, lib/hooks/useBlock*.svelte.ts — 13+ files, 5 different parents Pull all of document.transcription/* together.
C4.3 No Helper/Utils/Manager without modifier PARTIAL Minor lib/utils.ts (60-byte re-export) and lib/utils/ exist; component names look meaningful Delete lib/utils.ts; consider per-domain util grouping.
C4.4 API routes predictable PASS SvelteKit file-based routing makes this mechanical; minor exception: /users/[id] (Person profile) vs /admin/users (AppUser admin) — names collide Rename /users/[id]/persons/[id] (already exists) or merge; eliminate semantic overlap.
C5.1 Conventions doc explains adding a domain/endpoint/page FAIL Major frontend/CLAUDE.md covers patterns (API client, form actions, date handling, save bars) but not "how to add a new domain"; mostly tells the AI how, not a human Convert to README.
C5.2 One way for errors / API client / DTO PARTIAL Major API client pattern is consistent (see frontend/CLAUDE.md:60-80); error handling uses parseBackendError AND getErrorMessage AND raw result.error as unknown as { code? } cast at every call site (e.g. routes/admin/+layout.server.ts:38-48 repeats the cast 3×) — boilerplate begs a helper Extract okOrThrow(result) helper.
C5.3 CLAUDE.md rules mirrored in human docs FAIL Major frontend/CLAUDE.md is 198 lines of essential conventions; the human-facing frontend/README.md is the 39-line sv stub Move CLAUDE.md → README, retain CLAUDE.md as a thin pointer.
C5.4 Naming conventions stated and followed PARTIAL Minor PascalCase for components, kebab-case for routes — consistent in practice; no doc Document briefly in README.
C6.1 Backend controller→repo violations N/A Backend scope
C6.2 Backend service cross-domain repo N/A Backend scope
C6.3 Frontend domain components don't import siblings PARTIAL Major Within lib/components/document/* cousin imports are local and correct (DocumentEditLayout.svelte:11-13); lib/components/document/WhoWhenSection.svelte imports PersonTypeahead from lib/components/ (cross-domain into person) — this is expected per D-FE-1, but with no lib/person/ folder yet, the rule is unenforceable. lib/components/TranscriptionEditView.svelte:4 imports OcrTrigger (document.transcription→ocr — needs justification once domains exist) Allow with documented exceptions once lib/<domain>/ exists.
C6.4 Cross-cutting code in shared/ FAIL Major No shared/ exists; cross-cutting code is sprinkled across lib/components/ (e.g. BackButton, Pagination, LanguageSwitcher, ThemeToggle, ConfirmDialog, DateInput, HelpPopover) and lib/utils/, lib/services/, lib/actions/, lib/hooks/ Create lib/shared/.
C7.1 Per-domain README FAIL Major Zero domain READMEs in lib/; lib/components/document/, chronik/, user/ sub-dirs lack READMEs Add one per domain after REFACTOR-2.
C7.2 Why-comments on non-obvious decisions PARTIAL Major Several good ones (e.g. routes/+layout.svelte:21-27 justifies untrack for bulk-selection effect, lib/components/StammbaumTree.svelte:2-3 explains the eslint-disable). Many large components lack any why-comment — lib/components/StammbaumTree.svelte has 30+ lines of layout magic numbers (NODE_W=160, MIN_VIEWBOX_W=1200) without rationale; lib/utils/blockConflictMerge.ts is non-trivial and lightly commented Add why-comments to layout / merge / drag logic.
C7.3 No ask Marcel / Claude generated / TODO: figure out / non-obvious PARTIAL Major 2 TODOs only: routes/admin/+layout.server.ts:29 (legitimate, names the future endpoint) and lib/components/chronik/ChronikRow.svelte:163 (backend gap). Zero "ask Marcel"/"Claude generated"/"non-obvious" matches. Also: routes/api/tags/+server.ts:27 has a stray console.log('Tags Data', data) (dev artifact) Remove the console.log; resolve the two TODOs (file backend issues if needed).
C7.4 API contracts documented PASS lib/api.server.ts:1-15 documents the regen workflow; frontend/README.md:177-183 covers npm run generate:api; OpenAPI spec served by backend OK — but call out that lib/generated/api.ts is generated and must not be edited (it is gitignored per .gitignore:30-32 then re-added — confusing).
C7.5 DB schema per-table N/A Backend scope
C8.1 Mutation spot-check critical-path PARTIAL Critical Cannot run mutation tests in a read-only audit. Sampled spec quality: lib/components/Pagination.svelte.spec.ts, lib/components/PersonTypeahead.svelte.spec.ts, routes/+page.server.spec.ts (8995 bytes) all assert on observable behaviour, not implementation; lib/utils/mention.spec.ts (14k) covers many edge cases. Defer to manual mutation pass before REFACTOR-2. Mutation-validate the test suites that protect the components REFACTOR-2 will move (esp. Person*, TranscriptionBlock*, Annotation*, Document*, bulkSelection).
C8.2 E2E per critical journey PASS 33 specs in frontend/e2e/: auth.spec.ts, documents.spec.ts, persons.spec.ts, transcription.spec.ts, annotations.spec.ts, geschichten.spec.ts, briefwechsel-*.spec.ts, bulk-edit.spec.ts, permissions.spec.ts, accessibility.spec.ts, etc. — all critical journeys covered OK
C8.3 Test names descriptive PASS Spot check: back-button.spec.ts, notification-deep-link.spec.ts, person-mention-read.spec.ts — names predict scope OK
C8.4 No test ordering dependence FAIL Major playwright.config.ts:22-24 explicitly sets fullyParallel: false, workers: 1 and e2e/CLAUDE.md:43-48 confirms tests share auth state and must run sequentially. Vitest tests appear independent Document the constraint (already in e2e/CLAUDE.md); long-term, give each test its own auth context.
C9.1 Production deployment doc N/A Infra scope (AUDIT-5)
C9.2 Per-subsystem env reference FAIL Major Frontend env knobs (API_INTERNAL_URL, API_PROXY_TARGET, E2E_BASE_URL) are scattered across hooks.server.ts:47, vite.config.ts:18, playwright.config.ts:18,27; never listed in one place Add an "Environment variables" section to frontend README.
C9.3 Migration naming N/A Backend scope
C9.4 Logs/observability N/A — frontend Browser console only; no SSR log pipeline configured Note in deployment audit.
C10.1 No dead code FAIL Major OcrProgress.svelte (only translateOcrProgress is imported, the .svelte file is orphaned), DocumentStatusChip.svelte (zero imports), ExpandableText.svelte (zero imports), DashboardRecentDocuments.svelte (only its own spec imports it). src/lib/index.ts is the empty // place files … placeholder. lib/utils.ts is a 60-byte re-export. 6 paraglide_bak* dirs (~4 MB) that the build does not use. 3 stray test-results/screenshots/ dirs inside src/ (src/routes/test-results/, src/routes/login/test-results/, src/lib/components/test-results/). frontend/.svelte-kit.old/, .svelte-kit-backup/, test-results.locked/. routes/demo/ and routes/demo/paraglide/ are leftovers from sv create Delete all of the above (catalogued in §7).
C10.2 No premature abstractions PASS — sample Minor Components and hooks are concrete; no factory-of-factory smell observed OK
C10.3 No magic numbers PARTIAL Minor lib/components/StammbaumTree.svelte:19-27 defines layout constants but inline; widely OK elsewhere Acceptable.
C10.4 No half-finished features PARTIAL Major Two TODOs (see C7.3). One stray console.log in routes/api/tags/+server.ts:27. No if (false) blocks. routes/users/[id] and routes/admin/users/[id] co-exist with overlapping semantics — feels in-flight Resolve overlap.
C10.5 No defensive null-checks for impossible cases PASS — sample Minor API client pattern correctly uses result.data! after response.ok check. Defensive ?? null / ?? [] in routes/+page.svelte:36-60 is reasonable for SSR data OK

4. Subsystem health summary

Category PASS FAIL-Critical FAIL-Major FAIL-Minor FAIL-Cosmetic N/A
C1 First Contact 0 1 3 1 0 0
C2 Local Bootstrap 0 0 4 1 0 0
C3 Domain Clarity 0 3 1 0 0 1 (C3.4 backend)
C4 Findability 1 1 1 1 0 0
C5 Conventions 0 0 3 1 0 0
C6 Layering 0 0 2 0 0 2 (C6.1, C6.2 backend)
C7 Documentation 1 0 3 0 0 1 (C7.5 backend)
C8 Tests 2 0 1 0 0 1 (C8.1 deferred — see recommendation)
C9 Operability 0 0 1 0 0 3 (deployment/migrations/logs)
C10 Code Quality 2 0 2 1 0 0
Totals 6 5 21 5 0 8

Applicable checks: 37 of 50 (excluding 8 N/A and 5 deferred-to-other-audits in C2.3 / C9.1). PASS rate: 6 / 37 ≈ 16 %. Five Critical findings.

Overall: 🔴 (fails the threshold of "0 Critical" required for either 🟡 or 🟢).

The verdict is dominated by C1 / C3 / C4 — the lack of a real README, a real domain doc, and a domain-organised lib/. The code itself is largely clean (C6 / C8 / C10 sample fine); the legibility wrapper around the code is the gap.


5. Domain mapping gaps — every flat lib/components/ file

Every component currently in lib/components/ (root level) and its plausible target home. "Blocker question" only listed when non-trivial.

shared/ui (admission-criteria-clean — no entity, no CRUD)

Component Target Blocker
BackButton.svelte lib/shared/ui/
ConfirmDialog.svelte lib/shared/ui/ (paired with lib/services/confirm.svelte.ts)
DateInput.svelte lib/shared/ui/ Used only by document edit — could also live there
LanguageSwitcher.svelte lib/shared/i18n/
Pagination.svelte lib/shared/ui/
SortDropdown.svelte lib/shared/ui/
ThemeToggle.svelte lib/shared/ui/
OverflowPillButton.svelte OverflowPillDisplay.svelte lib/shared/ui/
ProgressRing.svelte lib/shared/ui/
ExpandableText.svelte lib/shared/ui/ DEAD — delete
GroupDivider.svelte lib/shared/ui/ or lib/document/ Used only by EnrichmentBlock
HelpPopover.svelte lib/shared/ui/
UnsavedWarningBanner.svelte lib/shared/ui/
UploadSuccessBanner.svelte lib/document/ (only used post-upload)
DistributionBar.svelte lib/shared/dashboard/ Used in dashboard widgets

shared/discussion (D-FE-3)

Component Target
MentionEditor.svelte, MentionDropdown.svelte lib/shared/discussion/
PersonMentionEditor.svelte + .test-host.svelte lib/shared/discussion/ (or lib/person/?) — blocker: is "person mention" a person concept or a discussion concept?
CommentMessage.svelte, CommentThread.svelte lib/shared/discussion/

person (D-FE-1)

Component Target Blocker
PersonChip.svelte, PersonChipRow.svelte lib/person/
PersonHoverCard.svelte lib/person/
PersonMultiSelect.svelte, PersonTypeahead.svelte lib/person/
PersonTypeBadge.svelte, PersonTypeSelector.svelte lib/person/
RelationshipChip.svelte, RelationshipPill.svelte lib/person/relationship/ Two near-duplicates — keep both?
AddRelationshipForm.svelte lib/person/relationship/
StammbaumCard.svelte, StammbaumSidePanel.svelte, StammbaumTree.svelte lib/person/relationship/
ContributorStack.svelte lib/person/ (avatar stack of contributors)

tag (D-FE-2)

Component Target
TagChipList.svelte, TagInput.svelte, TagParentPicker.svelte lib/tag/

document

Component Target Blocker
DocumentRow.svelte, DocumentThumbnail.svelte, DocumentTopBar.svelte, DocumentViewer.svelte lib/document/
DocumentMetadataDrawer.svelte, DocumentMultiSelect.svelte lib/document/ DocumentMetadataDrawer zero-import — verify before move
DocumentStatusChip.svelte lib/document/ DEAD — delete
ConversationThumbnail.svelte, ThumbnailRow.svelte lib/document/ (or conversation derived view?)
ReadyColumn.svelte, SegmentationColumn.svelte, TranscriptionColumn.svelte lib/document/ (mission-control board columns)
EnrichmentBlock.svelte lib/document/
PdfViewer.svelte, PdfControls.svelte lib/document/ (or shared/ui if reused for non-document PDFs — none currently)

document.annotation

Component Target
AnnotationLayer.svelte, AnnotationShape.svelte, AnnotationEditOverlay.svelte lib/document/annotation/

document.transcription

Component Target Blocker
TranscriptionBlock.svelte (+ .test-host.svelte), TranscriptionEditView.svelte, TranscriptionReadView.svelte, TranscriptionPanelHeader.svelte lib/document/transcription/
TranscribeCoachEmptyState.svelte, TranscribeDragDemo.svelte lib/document/transcription/ Onboarding — could also be a sub-folder
RichtlinienRuleCard.svelte, ScriptTypeSelect.svelte lib/document/transcription/

ocr

Component Target Blocker
OcrTrigger.svelte, OcrTrainingCard.svelte, SegmentationTrainingCard.svelte, TrainingHistory.svelte lib/ocr/
OcrProgress.svelte lib/ocr/ DEAD — delete (the file). The translateOcrProgress util is used; the component is not.

notification

Component Target
NotificationBell.svelte, NotificationDropdown.svelte lib/notification/

geschichte

Component Target
GeschichteEditor.svelte, GeschichtenCard.svelte lib/geschichte/

shared/dashboard (D-FE-4)

Component Target
MissionControlStrip.svelte, DashboardActivityFeed.svelte, DashboardFamilyPulse.svelte, DashboardResumeStrip.svelte, DashboardNeedsMetadata.svelte lib/shared/dashboard/
DashboardRecentDocuments.svelte DEAD — delete (only its spec imports it)

Page-local components co-located in routes/ (parallel paradigm)

The 39+ *.svelte files under src/routes/**/ (not +page.svelte / +layout.svelte) form a second organizational paradigm that competes with lib/components/. Examples: routes/AppNav.svelte, routes/briefwechsel/Conversation*.svelte, routes/persons/[id]/Person*.svelte, routes/admin/users/UsersListPanel.svelte, routes/profile/PasswordChangeForm.svelte. Blocker question: are these page-locals (legitimate co-location, leave alone) or should everything live under lib/<domain>/? REFACTOR-2 must answer this before moving anything.


6. Cross-cutting candidates for lib/shared/

Each item below satisfies the admission criteria: (a) no entity, (b) no user-facing CRUD, (c) ≥2 consumers OR framework infrastructure.

Item Justification Consumers
BackButton.svelte UI primitive, no entity, no CRUD 6+ pages
ConfirmDialog.svelte + lib/services/confirm.svelte.ts UI primitive (modal service) Layout-level singleton, used everywhere
Pagination.svelte, SortDropdown.svelte, DateInput.svelte, OverflowPill*.svelte, ProgressRing.svelte, HelpPopover.svelte, UnsavedWarningBanner.svelte Framework UI primitives ≥2 each
LanguageSwitcher.svelte, ThemeToggle.svelte Framework infra Layout
lib/api.server.ts Framework infra (typed API client factory) Every +page.server.ts
lib/errors.ts Cross-cutting error mapping Every page.server.ts that calls the API
lib/server/locale.ts, lib/relativeTime.ts, lib/utils/date.ts i18n / locale framework infra Many
lib/utils/debounce.ts, lib/utils/sort.ts, lib/utils/sanitize.ts, lib/utils/extractText.ts Generic utilities, no entity Many
lib/actions/clickOutside.ts, lib/actions/radioGroupNav.ts Reusable Svelte actions Many
lib/services/confirm.svelte.ts Singleton dialog service App-wide
MentionEditor.svelte, MentionDropdown.svelte, lib/utils/mention.ts, lib/utils/mentionSerializer.ts, lib/utils/comment.ts, lib/utils/commentDeepLink.ts Discussion primitives (D-FE-3 explicitly) Comments + Geschichten editor + transcription comments
MissionControlStrip.svelte, Dashboard*.svelte, DistributionBar.svelte Dashboard widgets (D-FE-4 explicitly) Home page only currently — borderline by criterion (c); accept as cross-cutting because the dashboard composes ≥3 domains

Items that should NOT go to shared/:

  • PersonTypeahead, PersonMultiSelect, PersonChip*, PersonHoverCard — bound to Person entity (D-FE-1: "cross-domain consumption is normal"), belong to lib/person/.
  • TagInput, TagChipList, TagParentPicker — bound to Tag entity (D-FE-2), belong to lib/tag/.
  • EnrichmentBlock, all Document* — bound to Document entity.

7. Dead/suspicious code register

File / Path Smell Action
frontend/src/lib/components/OcrProgress.svelte Component file is not imported anywhere; only the unrelated lib/ocr/translateOcrProgress.ts util is Delete the .svelte file
frontend/src/lib/components/DocumentStatusChip.svelte Zero imports across the codebase Delete
frontend/src/lib/components/ExpandableText.svelte Zero imports across the codebase Delete
frontend/src/lib/components/DashboardRecentDocuments.svelte Only its own .spec.ts references it Delete component + spec
frontend/src/lib/index.ts (line 1) Empty placeholder: // place files you want to import … Delete
frontend/src/lib/utils.ts (line 1) 60-byte re-export of isoToGerman, germanToIso from utils/date — useless redirection Delete; switch consumers to $lib/utils/date
frontend/src/lib/components/__mocks__/navigatingStore.ts (105 bytes) Test mock buried in production folder Move to src/lib/__mocks__/ or co-locate with the consuming spec
frontend/src/routes/demo/, frontend/src/routes/demo/paraglide/ sv create scaffold leftover, surfaces as /demo routes Delete folder
frontend/src/routes/api/tags/+server.ts:22, 27, 32 Two console.error and one console.log('Tags Data', data) (dev artifact) Remove console.log; replace console.error with proper error handling or accept
frontend/src/routes/admin/+layout.server.ts:29-30 TODO referencing missing backend endpoint /api/admin/stats File a Gitea issue, replace TODO with link
frontend/src/lib/components/chronik/ChronikRow.svelte:163 TODO about backend not exposing comment preview File a Gitea issue, replace TODO with link
frontend/src/routes/users/[id]/+page.svelte Semantic overlap with /admin/users/[id] (one is Person profile, one is AppUser admin) — confuses Person ≠ AppUser Decide: rename, merge, or document
frontend/.svelte-kit.old/ (24 KB) Stale generated dir at frontend root; eslint already ignores it (eslint.config.js:15) but it's still on disk Delete
frontend/.svelte-kit-backup/ (32 KB) Same Delete
frontend/test-results.locked/ (20 KB) Unexplained "locked" copy of test output Delete
frontend/proofshot-artifacts/ (68 KB) Local browser-verification artifacts; gitignored (.gitignore proofshot-artifacts/) but present on disk Delete on commit
frontend/src/lib/paraglide_bak/, paraglide_bak2/, paraglide_bak3/, paraglide_bak4/, paraglide_bak_b1_chown_pending/, paraglide_bak_now/ (≈4 MB total) 6 backup copies of generated paraglide output; gitignored via paraglide_bak* but pollute the source tree and confuse readers Delete all
frontend/src/routes/test-results/screenshots/ 3 PNGs nested inside the route tree; SvelteKit will not route them but they look like a /test-results route to a reader Move to frontend/test-results/ and gitignore
frontend/src/routes/login/test-results/screenshots/ Same Move/delete
frontend/src/lib/components/test-results/screenshots/ Same — 3 PNGs in lib/components/ Move/delete
frontend/README.md:1-39 Default # sv template from npx sv create ("If you're seeing this, you've probably already done this step. Congrats!") Replace with real README
frontend/src/lib/types.ts:38 DocumentPanelTab = 'metadata' | 'transcription' | 'discussion' | 'history' and other types are mixed-domain Split per domain
frontend/src/lib/components/PersonMentionEditor.test-host.svelte, TranscriptionBlock.test-host.svelte, lib/services/confirm.test-host.svelte Test hosts living in production component folder Convention: name them *.test-host.svelte and exclude — or move to __tests__/

Suspicious-but-not-dead (keep, but note):

  • frontend/src/lib/components/StammbaumTree.svelte:2-3 has a file-level eslint-disable svelte/prefer-svelte-reactivity — justified inline, OK.
  • frontend/src/routes/AppNav.svelte:143-147 has 3 svelte-ignore comments for a11y — overlay backdrop click; acceptable but worth a Skip-link audit.

8. Documentation gaps

  1. No real top-level README in frontend/. The 39-line # sv stub at frontend/README.md must be replaced. The substance currently lives in frontend/CLAUDE.md (198 lines: stack, project structure, API client pattern, form actions, date handling, styling, key components, run instructions, vite proxy, i18n) — every section there belongs in a human-facing README per C5.3.
  2. No per-domain README in lib/. Once REFACTOR-2 creates lib/<domain>/ directories, each needs a 1-paragraph "what it owns / does NOT own" file. Especially urgent for: person/ (vs AppUser), document/transcription/ (vs OCR), shared/discussion/ (the comment+mention split).
  3. No glossary. Person (historical) vs AppUser (login account), Geschichte (story), Briefwechsel (correspondence), Chronik (activity feed), Stammbaum (family tree), Richtlinien (transcription guidelines), Aktivitäten — bilingual terms confuse non-German speakers, even those who can read code.
  4. API contract docs. lib/generated/api.ts (137 KB) is generated from the backend OpenAPI spec; the frontend/CLAUDE.md:177-183 documents npm run generate:api, but the file header in lib/generated/api.ts should warn "DO NOT EDIT — regenerate via npm run generate:api" and the regen requires backend running with --spring.profiles.active=dev (mentioned only in CLAUDE.md). The .gitignore:30-32 situation (# src/lib/generated/api.ts commented out) is confusing.
  5. No env-var reference. API_INTERNAL_URL, API_PROXY_TARGET, E2E_BASE_URL, CI are referenced in hooks.server.ts:47, vite.config.ts:18, playwright.config.ts:18, playwright.config.ts:23 — never listed together.
  6. No conventions doc for: how to add a route, how to add an errors.ts code, how to wire a new API endpoint into the typed client. Anja and Tobias will have to grep.
  7. CLAUDE.md mentions stale path. Root /home/marcel/Desktop/familienarchiv/CLAUDE.md:218 documents conversations/ route; the actual path is briefwechsel/. This is exactly the "click live nav, not URLs from docs" risk previously logged.
  8. No why-comment for the DocumentStatus lifecycle on the frontend side, the bulkSelection clear-on-route-change behaviour (good comment in +layout.svelte:21-27, but the store itself is uncommented), or the handleFetch clone-and-rewrite dance in hooks.server.ts:91-101.

9. Prerequisites for big-bang refactor (REFACTOR-2 / #408)

Before any lib/ reorganisation:

  1. Mutation-validate the test suites that protect refactor candidates. At minimum: Person*.svelte.spec.ts, Tag*.svelte.spec.ts, Document*.svelte.spec.ts, Transcription*.svelte.{spec,test}.ts, Annotation*.svelte.{spec,test}.ts, bulkSelection.svelte.spec.ts, notifications.svelte.spec.ts, lib/utils/mention.spec.ts, lib/utils/blockConflictMerge.spec.ts, lib/utils/saveBlockWithConflictRetry.spec.ts. Use Stryker or hand-mutate one assertion per file as a smoke check.
  2. Resolve the page-local-vs-lib/ paradigm. Decide whether components used by exactly one page (the 39+ files currently inside routes/) stay co-located or move to lib/<domain>/. Without this rule, the refactor will be inconsistent.
  3. Delete the dead code in §7 first. Refactoring dead files multiplies diff noise.
  4. Settle the boundary for: PersonMentionEditor (person vs shared/discussion), EnrichmentBlock (document vs shared/dashboard), DistributionBar (shared/ui vs shared/dashboard), ConversationThumbnail (document vs conversation derived view), lib/types.ts (split or keep).
  5. Confirm path aliases. All move targets must work with $lib/<domain>/<File>.svelte. SvelteKit's $lib alias points to src/lib/ — no extra config needed, but verify tsconfig.json and eslint.config.js cope with new sub-folders.
  6. Stop new component additions in the flat lib/components/ bucket during the refactor window. Add a doc-only rule and a CI check (grep -E '\$lib/components/[^/]+\.svelte' should return zero in new diffs after REFACTOR-2 lands).
  7. Settle /users/[id] vs /admin/users/[id] semantic overlap. Don't move user-domain components while two user routes still bleed into each other (Person ≠ AppUser).
  8. Add lib/shared/ with at least one resident as a baseline before moving everything.

10. Top 5 prioritized recommendations

  1. Replace frontend/README.md with a real human-facing README built from the contents of frontend/CLAUDE.md (and add Anja-readable sections: domains, env vars, glossary entries, "how to add an X"). This single change resolves three Critical findings (C1.1, C2.5, C3.1) and unblocks everything else. Also write a top-level repo README.md per AUDIT-5 scope.
  2. Carve lib/components/ into lib/<domain>/{component,store,util,hook,type} per the canonical set (REFACTOR-2). Until this happens, any "find me the Person code" question fails. Use the §5 mapping verbatim; create lib/shared/{ui,discussion,dashboard,i18n} as the cross-cutting bucket.
  3. Run a one-day mutation-test pass on the 14 spec files that protect refactor candidates (listed in §9.1) and only then start moving files. This satisfies the C8.1 deferral and keeps REFACTOR-2 honest.
  4. Delete every item in §7 in a single "tree-clean" PR before REFACTOR-2 starts. ~4 MB of paraglide_bak*, the 4 dead components, frontend/src/lib/index.ts, frontend/src/lib/utils.ts, the 3 stray test-results/screenshots/ dirs inside src/, the frontend/.svelte-kit.old/, routes/demo/, the stray console.log, the two stale TODOs (filed as Gitea issues). Reviewing a refactor diff is much harder when half the moved files are dead.
  5. Add docs/GLOSSARY.md with the bilingual + Person ≠ AppUser distinctions and link it from both root README.md and frontend/README.md. Tobias will not get past Briefwechsel, Chronik, Stammbaum, Richtlinien, Aktivitäten, Geschichte without it; Anja will not get past PersonAppUser without it. Cheap, high-leverage, unblocks C3.3 (Critical).

Report generated under AUDIT-1, scope per #388, references #387 first comment. Read-only audit; no code modified.

# AUDIT-1 — Frontend Legibility Audit Read-only assessment of `frontend/` against `RUBRIC-LEGIBILITY-001`. Report follows `TEMPLATE-ORIENT-001`. Reference bundle: issue #387, first comment. --- ## 1. Subsystem profile The frontend is a **SvelteKit 2 + Svelte 5 (runes)** SSR app served by `@sveltejs/adapter-node`. It owns the entire web UI for the Familienarchiv: file-based routing under `src/routes/`, a typed API client (`openapi-fetch`) generated from the backend's OpenAPI spec into `src/lib/generated/api.ts`, Tailwind 4 styling with brand utilities defined in `src/routes/layout.css`, Paraglide i18n (de/en/es) compiled into `src/lib/paraglide/`, Vitest for unit + browser-mode component tests, and Playwright for E2E. Auth is cookie-based; SSR hooks in `src/hooks.server.ts` inject `Authorization` from `auth_token` and gate non-public routes. ``` frontend/ ├── src/ │ ├── routes/ # 43 +page.svelte, 35 +page.server.ts, 10 +layout files; 39 page-local *.svelte sub-components │ ├── lib/ │ │ ├── components/ # 79 flat components + 3 sub-dirs (document/, chronik/, user/) — primary AI-smell hotspot │ │ ├── generated/ # api.ts (do not edit) │ │ ├── stores/ services/ hooks/ utils/ actions/ ocr/ types/ server/ assets/ │ │ ├── paraglide/ # generated i18n │ │ ├── paraglide_bak{,2,3,4,_b1_chown_pending,_now}/ # 6 stale dirs (~4 MB) │ │ ├── api.server.ts errors.ts types.ts utils.ts relativeTime.ts search.ts person-validation.ts relationshipLabels.ts │ ├── hooks.server.ts hooks.ts app.d.ts app.html ├── e2e/ # 33 Playwright specs + auth.setup.ts ├── messages/ # de.json en.json es.json (1049 lines each, in sync) ├── README.md # ~40-line stub from `npx sv create` — does not describe Familienarchiv ├── CLAUDE.md # 198 lines, currently the de-facto frontend doc ├── .svelte-kit.old/ .svelte-kit-backup/ test-results.locked/ proofshot-artifacts/ # stale top-level dirs ``` --- ## 2. Inventory ### Routes (top-level) | Route | Domain | Notes | |---|---|---| | `/` | dashboard | Home dashboard; imports 5 dashboard widgets | | `/documents` `/documents/[id]` `/documents/[id]/edit` `/documents/new` `/documents/bulk-edit` | document | Full CRUD | | `/persons` `/persons/[id]` `/persons/[id]/edit` `/persons/new` | person | CRUD + merge | | `/stammbaum` | person.relationship | Tree view | | `/briefwechsel` | conversation (derived) | Bilateral timeline; route name is German | | `/aktivitaeten` | activity (derived) | Chronik / activity feed | | `/geschichten` `/geschichten/[id]` `/geschichten/[id]/edit` `/geschichten/new` | geschichte | CRUD | | `/enrich` `/enrich/[id]` `/enrich/done` | document (sub-flow) | Bulk metadata enrichment | | `/admin` `/admin/groups` `/admin/users` `/admin/tags` `/admin/ocr` `/admin/invites` `/admin/system` | user / tag / ocr (admin views) | Six sub-areas | | `/users/[id]` | user | Public profile view — overlaps semantically with `/admin/users/[id]` | | `/profile` | user | Self-service | | `/login` `/logout` `/register` `/forgot-password` `/reset-password` | security (cross-cutting) | Auth flows | | `/hilfe` `/hilfe/transkription` | shared/help | Static help pages | | `/api/*` `/api/[...path]` `/api/documents` `/api/persons` `/api/tags` | shared (server proxy) | SSR-only endpoints | | `/demo` `/demo/paraglide` | — | Scaffold leftover from `sv create` | ### `lib/` root files | File | Purpose | Apparent home | |---|---|---| | `api.server.ts` | Typed OpenAPI client factory | shared/ (framework infra) | | `errors.ts` | `ErrorCode` mirror + paraglide lookup | shared/error-handling | | `types.ts` | Comment / Mention / TranscriptionBlockData / Annotation types | document (mostly), person (Mention) — currently mixed | | `utils.ts` | Re-exports `isoToGerman` from `utils/date` (60-byte stub) | shared/ — orphan vestige | | `relativeTime.ts` | "vor 3 Tagen" formatter | shared/i18n | | `search.ts` | URL search-param helpers | shared/ | | `person-validation.ts` | Person form validation | person/ | | `relationshipLabels.ts` | Relationship enum → German label | person.relationship | | `index.ts` | Empty placeholder — `// place files you want to import …` | dead | ### Components currently in `lib/components/` (root level — 79 files) The flat dir mixes ≥9 domains and 3 cross-cutting categories. Domain assignment per the canonical set: | Component | Domain | |---|---| | `BackButton`, `ConfirmDialog`, `DateInput`, `LanguageSwitcher`, `Pagination`, `SortDropdown`, `ThemeToggle`, `OverflowPillButton`, `OverflowPillDisplay`, `ProgressRing`, `ExpandableText`, `GroupDivider`, `HelpPopover`, `UnsavedWarningBanner`, `UploadSuccessBanner`, `DistributionBar` | shared/ui | | `MentionEditor`, `MentionDropdown`, `PersonMentionEditor`, `PersonMentionEditor.test-host`, `CommentMessage`, `CommentThread` | shared/discussion (per D-FE-3) | | `PersonChip`, `PersonChipRow`, `PersonHoverCard`, `PersonMultiSelect`, `PersonTypeahead`, `PersonTypeBadge`, `PersonTypeSelector`, `RelationshipChip`, `RelationshipPill`, `AddRelationshipForm`, `StammbaumCard`, `StammbaumSidePanel`, `StammbaumTree`, `ContributorStack` | person (D-FE-1; relationship is sub-package) | | `TagChipList`, `TagInput`, `TagParentPicker` | tag (D-FE-2) | | `DocumentRow`, `DocumentThumbnail`, `DocumentTopBar`, `DocumentViewer`, `DocumentMetadataDrawer`, `DocumentMultiSelect`, `DocumentStatusChip`, `ConversationThumbnail`, `ThumbnailRow`, `ReadyColumn`, `SegmentationColumn`, `TranscriptionColumn`, `EnrichmentBlock`, `PdfViewer`, `PdfControls`, `FileSwitcherStrip` (in document/) | document | | `AnnotationLayer`, `AnnotationShape`, `AnnotationEditOverlay` | document.annotation | | `TranscriptionBlock`, `TranscriptionBlock.test-host`, `TranscriptionEditView`, `TranscriptionReadView`, `TranscriptionPanelHeader`, `TranscribeCoachEmptyState`, `TranscribeDragDemo`, `RichtlinienRuleCard`, `ScriptTypeSelect` | document.transcription | | `OcrTrigger`, `OcrProgress`, `OcrTrainingCard`, `SegmentationTrainingCard`, `TrainingHistory` | ocr | | `NotificationBell`, `NotificationDropdown` | notification | | `GeschichteEditor`, `GeschichtenCard` | geschichte | | `MissionControlStrip`, `DashboardActivityFeed`, `DashboardFamilyPulse`, `DashboardNeedsMetadata`, `DashboardRecentDocuments`, `DashboardResumeStrip` | shared/dashboard (D-FE-4) | Sub-directories: | Dir | Members | Domain | |---|---|---| | `lib/components/document/` | 14 files (BulkDocumentEditLayout, BulkDropZone, BulkSelectionBar, DescriptionSection, DocumentEditLayout, FieldLabelBadge, FileSwitcherStrip, ScopeCard, TranscriptionSection, UploadSaveBar, UploadZone, WhoWhenSection) | document | | `lib/components/chronik/` | ChronikEmptyState, ChronikErrorCard, ChronikFilterPills, ChronikFuerDichBox, ChronikRow, ChronikTimeline | activity (derived) | | `lib/components/user/` | UserGroupsSection, UserPasswordSection, UserProfileSection (3 files, no tests) | user | ### Stores, hooks, utils, services, actions | Path | Files | Domain | |---|---|---| | `lib/stores/` | `bulkSelection.svelte.ts` (document), `notifications.svelte.ts` (notification) | document + notification | | `lib/hooks/` | `useBlockAutoSave`, `useBlockDragDrop`, `useFileLoader`, `usePdfRenderer`, `useTypeahead`, `useUnsavedWarning` | mostly document.transcription + shared | | `lib/services/` | `confirm.svelte.ts` + test-host | shared/ui | | `lib/actions/` | `clickOutside.ts`, `radioGroupNav.ts` | shared/ui | | `lib/utils/` | 24 ts files: `date`, `debounce`, `sort`, `sanitize`, `notifications`, `requiredFields`, `validateFile`, `filename`, `time`, `documentStatusLabel`, `personFormat`, `personLifeDates`, `groupDocuments`, `comment`, `commentDeepLink`, `mention`, `mentionSerializer`, `transcriptionMarkers`, `extractText`, `blockConflictMerge`, `saveBlockWithConflictRetry`, `deepLinkScroll`, `hoverCardPosition`, `date-buckets` | mixed (≥7 domains share this single bucket) | | `lib/types/` | `personHoverCard.ts`, `training.ts` | person, ocr | | `lib/ocr/` | `translateOcrProgress.ts` | ocr | | `lib/server/` | `locale.ts` | shared/i18n | ### Page-local components co-located in `src/routes/` (≥39 files) - `routes/AppNav.svelte`, `AuthHeader.svelte`, `UserMenu.svelte`, `DocumentList.svelte`, `DropZone.svelte`, `SearchFilterBar.svelte` — shell components living next to the layout - `routes/admin/{EntityNav,EntityNavSection}.svelte`, `routes/admin/{groups,tags,users,ocr}/…` — many `*ListPanel.svelte`, `Tag*.svelte`, `OcrHealthBar.svelte`, etc. - `routes/briefwechsel/{ConversationFilterBar,ConversationTimeline,Correspondent…,Correspondenz…,SinglePersonHintBar}.svelte` (7 files) - `routes/persons/[id]/{PersonCard,PersonDocumentList,PersonRelationshipsCard,PersonMergePanel,CoCorrespondentsList,NameHistoryCard}.svelte` + edit subfolder - `routes/profile/{PersonalInfoForm,PasswordChangeForm}.svelte` This co-location is **a second, parallel organizational paradigm** to `lib/components/` — the codebase has not chosen one. (See §5.) --- ## 3. Rubric scorecard | Check | Result | Severity | Evidence | Recommendation | |---|---|---|---|---| | **C1.1** Root README explains purpose ≤3 sentences | FAIL | Critical | No `README.md` at repo root; `frontend/README.md:1-39` is the unmodified `sv create` template (`# sv` … "If you're seeing this, you've probably already done this step. Congrats!") | Write `README.md` at repo root with one-paragraph product description; replace `frontend/README.md` stub (or rely on `frontend/CLAUDE.md` content moved to README). Overlap with AUDIT-5; flagging here per scope. | | **C1.2** README lists 5 components | FAIL | Major | `frontend/README.md:1-39` has none | Add component map (frontend/backend/OCR/DB/infra) to root README. | | **C1.3** `docs/ARCHITECTURE.md` with diagram | FAIL | Major | No `docs/ARCHITECTURE.md`; `docs/architecture/c4-diagrams.md` exists but is not linked from any README | Create or link the C4 diagrams from a top-level `ARCHITECTURE.md`. | | **C1.4** README links to run / contribute / issues | FAIL | Major | `frontend/README.md` has none of these; `COLLABORATING.md` and `docs/` exist but are not linked | Cross-link from `frontend/README.md`. | | **C1.5** Repo root free of stray/old folders | FAIL | Minor | `frontend/.svelte-kit.old/`, `.svelte-kit-backup/`, `test-results.locked/`, `proofshot-artifacts/` (committed-or-tracked); 6 × `src/lib/paraglide_bak*/` (~4 MB) | Delete or `.gitignore` properly. | | **C2.1** Single command brings stack up | PARTIAL/FAIL | Major | `frontend/README.md:18-26` says `npm run dev` only; no instructions to run backend/MinIO/DB; `frontend/CLAUDE.md:142-150` does point to root `docker-compose up -d` | Move CLAUDE.md run instructions into README. | | **C2.2** Prerequisites with versions | PARTIAL | Major | `frontend/README.md` has none; `frontend/CLAUDE.md:7-19` lists Svelte 5, TS 5.9, Vite 7 (no Node version) | Add Node version (≥24 per package.json `@types/node`) to README. | | **C2.3** Default credentials documented | UNKNOWN | Major | Neither `frontend/README.md` nor `frontend/CLAUDE.md` mentions `admin@familyarchive.local / admin123`; defer to AUDIT-5 | Document in root README. | | **C2.4** Common failure modes | FAIL | Minor | Only `frontend/e2e/CLAUDE.md:130-138` has a small troubleshooting table; no frontend-wide section | Add to frontend README. | | **C2.5** Per-subsystem README | PARTIAL | Major | `frontend/README.md` exists but is the empty `sv` stub; `frontend/e2e/CLAUDE.md` is good but is CLAUDE-marked, not human-marked | Promote `frontend/CLAUDE.md` content into `frontend/README.md`. | | **C3.1** Domains stated in docs | PARTIAL | Critical | `frontend/CLAUDE.md:38-56` lists routes but does not name domains as bounded contexts | Add a "Domains" section (the canonical 7+2 list from #387). | | **C3.2** One-paragraph definitions per domain | FAIL | Major | None present | Per-domain README under `lib/<domain>/README.md` (see §8). | | **C3.3** Glossary disambiguates Person ≠ AppUser | FAIL | Critical | No glossary file in `frontend/`; tension is real (`/users/[id]` is a Person profile and `/admin/users/[id]` is an AppUser) | Add `docs/GLOSSARY.md` and link from frontend README. Critical for Anja & Tobias. | | **C3.4** Backend domain-based packages | N/A | — | Backend audit (AUDIT-2) | — | | **C3.5** Frontend `lib/` domain-based, names stack-symmetric | FAIL | Critical | `lib/components/` is a flat 79-file bucket spanning ≥9 domains; only `document/`, `chronik/`, `user/` sub-dirs exist; `lib/utils/` mixes mention, transcription, document, person, file, sort, dates, etc. (24 files in one folder) | This is the central refactor target — REFACTOR-2 (#408). | | **C3.6** `shared/` justified | N/A — `shared/` does not exist yet | Major | No `lib/shared/` exists; cross-cutting code is currently in `lib/` root or scattered | Create `lib/shared/{ui,discussion,dashboard,error-handling,i18n}` per D-FE-3/4. | | **C4.1** Locate every file for a domain ≤2 min | FAIL | Critical | `Person`-touching code is in: `lib/components/Person*.svelte` (7 files), `lib/components/Stammbaum*.svelte` (3 files), `lib/components/AddRelationshipForm.svelte`, `lib/utils/personFormat.ts`, `lib/utils/personLifeDates.ts`, `lib/person-validation.ts`, `lib/relationshipLabels.ts`, `lib/types/personHoverCard.ts`, `routes/persons/`, `routes/persons/[id]/Person*.svelte` (page-local) — minimum **6 different parents** to scan | Consolidate under `lib/person/` per REFACTOR-2. | | **C4.2** Locate controller+service+page+component for a feature ≤5 min | PARTIAL | Major | Front-end-only legibility for a feature like "transcription" is hard: `TranscriptionBlock.svelte`, `TranscriptionEditView.svelte`, `TranscriptionReadView.svelte`, `TranscriptionPanelHeader.svelte`, `TranscriptionColumn.svelte`, `TranscribeCoachEmptyState.svelte`, `TranscribeDragDemo.svelte`, `RichtlinienRuleCard.svelte`, `ScriptTypeSelect.svelte`, `lib/utils/transcriptionMarkers.ts`, `lib/utils/blockConflictMerge.ts`, `lib/utils/saveBlockWithConflictRetry.ts`, `lib/hooks/useBlock*.svelte.ts` — 13+ files, 5 different parents | Pull all of `document.transcription/*` together. | | **C4.3** No `Helper`/`Utils`/`Manager` without modifier | PARTIAL | Minor | `lib/utils.ts` (60-byte re-export) and `lib/utils/` exist; component names look meaningful | Delete `lib/utils.ts`; consider per-domain util grouping. | | **C4.4** API routes predictable | PASS | — | SvelteKit file-based routing makes this mechanical; minor exception: `/users/[id]` (Person profile) vs `/admin/users` (AppUser admin) — names collide | Rename `/users/[id]` → `/persons/[id]` (already exists) or merge; eliminate semantic overlap. | | **C5.1** Conventions doc explains adding a domain/endpoint/page | FAIL | Major | `frontend/CLAUDE.md` covers patterns (API client, form actions, date handling, save bars) but not "how to add a new domain"; mostly tells the AI how, not a human | Convert to README. | | **C5.2** One way for errors / API client / DTO | PARTIAL | Major | API client pattern is consistent (see `frontend/CLAUDE.md:60-80`); error handling uses `parseBackendError` AND `getErrorMessage` AND raw `result.error as unknown as { code? }` cast at every call site (e.g. `routes/admin/+layout.server.ts:38-48` repeats the cast 3×) — boilerplate begs a helper | Extract `okOrThrow(result)` helper. | | **C5.3** CLAUDE.md rules mirrored in human docs | FAIL | Major | `frontend/CLAUDE.md` is 198 lines of essential conventions; the human-facing `frontend/README.md` is the 39-line `sv` stub | Move CLAUDE.md → README, retain CLAUDE.md as a thin pointer. | | **C5.4** Naming conventions stated and followed | PARTIAL | Minor | PascalCase for components, kebab-case for routes — consistent in practice; no doc | Document briefly in README. | | **C6.1** Backend controller→repo violations | N/A | — | Backend scope | — | | **C6.2** Backend service cross-domain repo | N/A | — | Backend scope | — | | **C6.3** Frontend domain components don't import siblings | PARTIAL | Major | Within `lib/components/document/*` cousin imports are local and correct (`DocumentEditLayout.svelte:11-13`); `lib/components/document/WhoWhenSection.svelte` imports `PersonTypeahead` from `lib/components/` (cross-domain into person) — this is expected per D-FE-1, but with no `lib/person/` folder yet, the rule is unenforceable. `lib/components/TranscriptionEditView.svelte:4` imports `OcrTrigger` (document.transcription→ocr — needs justification once domains exist) | Allow with documented exceptions once `lib/<domain>/` exists. | | **C6.4** Cross-cutting code in `shared/` | FAIL | Major | No `shared/` exists; cross-cutting code is sprinkled across `lib/components/` (e.g. `BackButton`, `Pagination`, `LanguageSwitcher`, `ThemeToggle`, `ConfirmDialog`, `DateInput`, `HelpPopover`) and `lib/utils/`, `lib/services/`, `lib/actions/`, `lib/hooks/` | Create `lib/shared/`. | | **C7.1** Per-domain README | FAIL | Major | Zero domain READMEs in `lib/`; `lib/components/document/`, `chronik/`, `user/` sub-dirs lack READMEs | Add one per domain after REFACTOR-2. | | **C7.2** Why-comments on non-obvious decisions | PARTIAL | Major | Several good ones (e.g. `routes/+layout.svelte:21-27` justifies `untrack` for bulk-selection effect, `lib/components/StammbaumTree.svelte:2-3` explains the eslint-disable). Many large components lack any why-comment — `lib/components/StammbaumTree.svelte` has 30+ lines of layout magic numbers (`NODE_W=160`, `MIN_VIEWBOX_W=1200`) without rationale; `lib/utils/blockConflictMerge.ts` is non-trivial and lightly commented | Add why-comments to layout / merge / drag logic. | | **C7.3** No `ask Marcel` / `Claude generated` / `TODO: figure out` / `non-obvious` | PARTIAL | Major | 2 TODOs only: `routes/admin/+layout.server.ts:29` (legitimate, names the future endpoint) and `lib/components/chronik/ChronikRow.svelte:163` (backend gap). Zero "ask Marcel"/"Claude generated"/"non-obvious" matches. Also: `routes/api/tags/+server.ts:27` has a stray `console.log('Tags Data', data)` (dev artifact) | Remove the console.log; resolve the two TODOs (file backend issues if needed). | | **C7.4** API contracts documented | PASS | — | `lib/api.server.ts:1-15` documents the regen workflow; `frontend/README.md:177-183` covers `npm run generate:api`; OpenAPI spec served by backend | OK — but call out that `lib/generated/api.ts` is generated and must not be edited (it is gitignored per `.gitignore:30-32` then re-added — confusing). | | **C7.5** DB schema per-table | N/A | — | Backend scope | — | | **C8.1** Mutation spot-check critical-path | PARTIAL | Critical | Cannot run mutation tests in a read-only audit. Sampled spec quality: `lib/components/Pagination.svelte.spec.ts`, `lib/components/PersonTypeahead.svelte.spec.ts`, `routes/+page.server.spec.ts` (8995 bytes) all assert on observable behaviour, not implementation; `lib/utils/mention.spec.ts` (14k) covers many edge cases. **Defer to manual mutation pass before REFACTOR-2.** | Mutation-validate the test suites that protect the components REFACTOR-2 will move (esp. `Person*`, `TranscriptionBlock*`, `Annotation*`, `Document*`, `bulkSelection`). | | **C8.2** E2E per critical journey | PASS | — | 33 specs in `frontend/e2e/`: `auth.spec.ts`, `documents.spec.ts`, `persons.spec.ts`, `transcription.spec.ts`, `annotations.spec.ts`, `geschichten.spec.ts`, `briefwechsel-*.spec.ts`, `bulk-edit.spec.ts`, `permissions.spec.ts`, `accessibility.spec.ts`, etc. — all critical journeys covered | OK | | **C8.3** Test names descriptive | PASS | — | Spot check: `back-button.spec.ts`, `notification-deep-link.spec.ts`, `person-mention-read.spec.ts` — names predict scope | OK | | **C8.4** No test ordering dependence | FAIL | Major | `playwright.config.ts:22-24` explicitly sets `fullyParallel: false, workers: 1` and `e2e/CLAUDE.md:43-48` confirms tests share auth state and **must run sequentially**. Vitest tests appear independent | Document the constraint (already in e2e/CLAUDE.md); long-term, give each test its own auth context. | | **C9.1** Production deployment doc | N/A | — | Infra scope (AUDIT-5) | — | | **C9.2** Per-subsystem env reference | FAIL | Major | Frontend env knobs (`API_INTERNAL_URL`, `API_PROXY_TARGET`, `E2E_BASE_URL`) are scattered across `hooks.server.ts:47`, `vite.config.ts:18`, `playwright.config.ts:18,27`; never listed in one place | Add an "Environment variables" section to frontend README. | | **C9.3** Migration naming | N/A | — | Backend scope | — | | **C9.4** Logs/observability | N/A — frontend | — | Browser console only; no SSR log pipeline configured | Note in deployment audit. | | **C10.1** No dead code | FAIL | Major | `OcrProgress.svelte` (only `translateOcrProgress` is imported, the .svelte file is orphaned), `DocumentStatusChip.svelte` (zero imports), `ExpandableText.svelte` (zero imports), `DashboardRecentDocuments.svelte` (only its own spec imports it). `src/lib/index.ts` is the empty `// place files …` placeholder. `lib/utils.ts` is a 60-byte re-export. **6 paraglide_bak\* dirs** (~4 MB) that the build does not use. **3 stray `test-results/screenshots/` dirs inside `src/`** (`src/routes/test-results/`, `src/routes/login/test-results/`, `src/lib/components/test-results/`). `frontend/.svelte-kit.old/`, `.svelte-kit-backup/`, `test-results.locked/`. `routes/demo/` and `routes/demo/paraglide/` are leftovers from `sv create` | Delete all of the above (catalogued in §7). | | **C10.2** No premature abstractions | PASS — sample | Minor | Components and hooks are concrete; no factory-of-factory smell observed | OK | | **C10.3** No magic numbers | PARTIAL | Minor | `lib/components/StammbaumTree.svelte:19-27` defines layout constants but inline; widely OK elsewhere | Acceptable. | | **C10.4** No half-finished features | PARTIAL | Major | Two TODOs (see C7.3). One stray `console.log` in `routes/api/tags/+server.ts:27`. No `if (false)` blocks. `routes/users/[id]` and `routes/admin/users/[id]` co-exist with overlapping semantics — feels in-flight | Resolve overlap. | | **C10.5** No defensive null-checks for impossible cases | PASS — sample | Minor | API client pattern correctly uses `result.data!` after `response.ok` check. Defensive `?? null` / `?? []` in `routes/+page.svelte:36-60` is reasonable for SSR data | OK | --- ## 4. Subsystem health summary | Category | PASS | FAIL-Critical | FAIL-Major | FAIL-Minor | FAIL-Cosmetic | N/A | |---|---:|---:|---:|---:|---:|---:| | C1 First Contact | 0 | 1 | 3 | 1 | 0 | 0 | | C2 Local Bootstrap | 0 | 0 | 4 | 1 | 0 | 0 | | C3 Domain Clarity | 0 | 3 | 1 | 0 | 0 | 1 (C3.4 backend) | | C4 Findability | 1 | 1 | 1 | 1 | 0 | 0 | | C5 Conventions | 0 | 0 | 3 | 1 | 0 | 0 | | C6 Layering | 0 | 0 | 2 | 0 | 0 | 2 (C6.1, C6.2 backend) | | C7 Documentation | 1 | 0 | 3 | 0 | 0 | 1 (C7.5 backend) | | C8 Tests | 2 | 0 | 1 | 0 | 0 | 1 (C8.1 deferred — see recommendation) | | C9 Operability | 0 | 0 | 1 | 0 | 0 | 3 (deployment/migrations/logs) | | C10 Code Quality | 2 | 0 | 2 | 1 | 0 | 0 | | **Totals** | **6** | **5** | **21** | **5** | **0** | **8** | Applicable checks: 37 of 50 (excluding 8 N/A and 5 deferred-to-other-audits in C2.3 / C9.1). PASS rate: 6 / 37 ≈ **16 %**. Five Critical findings. **Overall: 🔴** (fails the threshold of "0 Critical" required for either 🟡 or 🟢). The verdict is dominated by C1 / C3 / C4 — the lack of a real README, a real domain doc, and a domain-organised `lib/`. The code itself is largely clean (C6 / C8 / C10 sample fine); the **legibility wrapper around the code is the gap**. --- ## 5. Domain mapping gaps — every flat `lib/components/` file Every component currently in `lib/components/` (root level) and its plausible target home. "Blocker question" only listed when non-trivial. ### shared/ui (admission-criteria-clean — no entity, no CRUD) | Component | Target | Blocker | |---|---|---| | `BackButton.svelte` | `lib/shared/ui/` | — | | `ConfirmDialog.svelte` | `lib/shared/ui/` (paired with `lib/services/confirm.svelte.ts`) | — | | `DateInput.svelte` | `lib/shared/ui/` | Used only by document edit — could also live there | | `LanguageSwitcher.svelte` | `lib/shared/i18n/` | — | | `Pagination.svelte` | `lib/shared/ui/` | — | | `SortDropdown.svelte` | `lib/shared/ui/` | — | | `ThemeToggle.svelte` | `lib/shared/ui/` | — | | `OverflowPillButton.svelte` `OverflowPillDisplay.svelte` | `lib/shared/ui/` | — | | `ProgressRing.svelte` | `lib/shared/ui/` | — | | `ExpandableText.svelte` | `lib/shared/ui/` | **DEAD — delete** | | `GroupDivider.svelte` | `lib/shared/ui/` or `lib/document/` | Used only by `EnrichmentBlock` | | `HelpPopover.svelte` | `lib/shared/ui/` | — | | `UnsavedWarningBanner.svelte` | `lib/shared/ui/` | — | | `UploadSuccessBanner.svelte` | `lib/document/` (only used post-upload) | — | | `DistributionBar.svelte` | `lib/shared/dashboard/` | Used in dashboard widgets | ### shared/discussion (D-FE-3) | Component | Target | |---|---| | `MentionEditor.svelte`, `MentionDropdown.svelte` | `lib/shared/discussion/` | | `PersonMentionEditor.svelte` + `.test-host.svelte` | `lib/shared/discussion/` (or `lib/person/`?) — **blocker:** is "person mention" a person concept or a discussion concept? | | `CommentMessage.svelte`, `CommentThread.svelte` | `lib/shared/discussion/` | ### person (D-FE-1) | Component | Target | Blocker | |---|---|---| | `PersonChip.svelte`, `PersonChipRow.svelte` | `lib/person/` | — | | `PersonHoverCard.svelte` | `lib/person/` | — | | `PersonMultiSelect.svelte`, `PersonTypeahead.svelte` | `lib/person/` | — | | `PersonTypeBadge.svelte`, `PersonTypeSelector.svelte` | `lib/person/` | — | | `RelationshipChip.svelte`, `RelationshipPill.svelte` | `lib/person/relationship/` | Two near-duplicates — keep both? | | `AddRelationshipForm.svelte` | `lib/person/relationship/` | — | | `StammbaumCard.svelte`, `StammbaumSidePanel.svelte`, `StammbaumTree.svelte` | `lib/person/relationship/` | — | | `ContributorStack.svelte` | `lib/person/` (avatar stack of contributors) | — | ### tag (D-FE-2) | Component | Target | |---|---| | `TagChipList.svelte`, `TagInput.svelte`, `TagParentPicker.svelte` | `lib/tag/` | ### document | Component | Target | Blocker | |---|---|---| | `DocumentRow.svelte`, `DocumentThumbnail.svelte`, `DocumentTopBar.svelte`, `DocumentViewer.svelte` | `lib/document/` | — | | `DocumentMetadataDrawer.svelte`, `DocumentMultiSelect.svelte` | `lib/document/` | `DocumentMetadataDrawer` zero-import — verify before move | | `DocumentStatusChip.svelte` | `lib/document/` | **DEAD — delete** | | `ConversationThumbnail.svelte`, `ThumbnailRow.svelte` | `lib/document/` (or conversation derived view?) | — | | `ReadyColumn.svelte`, `SegmentationColumn.svelte`, `TranscriptionColumn.svelte` | `lib/document/` (mission-control board columns) | — | | `EnrichmentBlock.svelte` | `lib/document/` | — | | `PdfViewer.svelte`, `PdfControls.svelte` | `lib/document/` (or shared/ui if reused for non-document PDFs — none currently) | — | ### document.annotation | Component | Target | |---|---| | `AnnotationLayer.svelte`, `AnnotationShape.svelte`, `AnnotationEditOverlay.svelte` | `lib/document/annotation/` | ### document.transcription | Component | Target | Blocker | |---|---|---| | `TranscriptionBlock.svelte` (+ `.test-host.svelte`), `TranscriptionEditView.svelte`, `TranscriptionReadView.svelte`, `TranscriptionPanelHeader.svelte` | `lib/document/transcription/` | — | | `TranscribeCoachEmptyState.svelte`, `TranscribeDragDemo.svelte` | `lib/document/transcription/` | Onboarding — could also be a sub-folder | | `RichtlinienRuleCard.svelte`, `ScriptTypeSelect.svelte` | `lib/document/transcription/` | — | ### ocr | Component | Target | Blocker | |---|---|---| | `OcrTrigger.svelte`, `OcrTrainingCard.svelte`, `SegmentationTrainingCard.svelte`, `TrainingHistory.svelte` | `lib/ocr/` | — | | `OcrProgress.svelte` | `lib/ocr/` | **DEAD — delete (the file). The `translateOcrProgress` util is used; the component is not.** | ### notification | Component | Target | |---|---| | `NotificationBell.svelte`, `NotificationDropdown.svelte` | `lib/notification/` | ### geschichte | Component | Target | |---|---| | `GeschichteEditor.svelte`, `GeschichtenCard.svelte` | `lib/geschichte/` | ### shared/dashboard (D-FE-4) | Component | Target | |---|---| | `MissionControlStrip.svelte`, `DashboardActivityFeed.svelte`, `DashboardFamilyPulse.svelte`, `DashboardResumeStrip.svelte`, `DashboardNeedsMetadata.svelte` | `lib/shared/dashboard/` | | `DashboardRecentDocuments.svelte` | **DEAD — delete (only its spec imports it)** | ### Page-local components co-located in `routes/` (parallel paradigm) The 39+ `*.svelte` files under `src/routes/**/` (not `+page.svelte` / `+layout.svelte`) form a **second organizational paradigm** that competes with `lib/components/`. Examples: `routes/AppNav.svelte`, `routes/briefwechsel/Conversation*.svelte`, `routes/persons/[id]/Person*.svelte`, `routes/admin/users/UsersListPanel.svelte`, `routes/profile/PasswordChangeForm.svelte`. **Blocker question:** are these page-locals (legitimate co-location, leave alone) or should everything live under `lib/<domain>/`? REFACTOR-2 must answer this before moving anything. --- ## 6. Cross-cutting candidates for `lib/shared/` Each item below satisfies the admission criteria: (a) no entity, (b) no user-facing CRUD, (c) ≥2 consumers OR framework infrastructure. | Item | Justification | Consumers | |---|---|---| | `BackButton.svelte` | UI primitive, no entity, no CRUD | 6+ pages | | `ConfirmDialog.svelte` + `lib/services/confirm.svelte.ts` | UI primitive (modal service) | Layout-level singleton, used everywhere | | `Pagination.svelte`, `SortDropdown.svelte`, `DateInput.svelte`, `OverflowPill*.svelte`, `ProgressRing.svelte`, `HelpPopover.svelte`, `UnsavedWarningBanner.svelte` | Framework UI primitives | ≥2 each | | `LanguageSwitcher.svelte`, `ThemeToggle.svelte` | Framework infra | Layout | | `lib/api.server.ts` | Framework infra (typed API client factory) | Every `+page.server.ts` | | `lib/errors.ts` | Cross-cutting error mapping | Every page.server.ts that calls the API | | `lib/server/locale.ts`, `lib/relativeTime.ts`, `lib/utils/date.ts` | i18n / locale framework infra | Many | | `lib/utils/debounce.ts`, `lib/utils/sort.ts`, `lib/utils/sanitize.ts`, `lib/utils/extractText.ts` | Generic utilities, no entity | Many | | `lib/actions/clickOutside.ts`, `lib/actions/radioGroupNav.ts` | Reusable Svelte actions | Many | | `lib/services/confirm.svelte.ts` | Singleton dialog service | App-wide | | `MentionEditor.svelte`, `MentionDropdown.svelte`, `lib/utils/mention.ts`, `lib/utils/mentionSerializer.ts`, `lib/utils/comment.ts`, `lib/utils/commentDeepLink.ts` | Discussion primitives (D-FE-3 explicitly) | Comments + Geschichten editor + transcription comments | | `MissionControlStrip.svelte`, `Dashboard*.svelte`, `DistributionBar.svelte` | Dashboard widgets (D-FE-4 explicitly) | Home page only currently — borderline by criterion (c); accept as cross-cutting because the dashboard composes ≥3 domains | Items that should **NOT** go to `shared/`: - `PersonTypeahead`, `PersonMultiSelect`, `PersonChip*`, `PersonHoverCard` — bound to `Person` entity (D-FE-1: "cross-domain consumption is normal"), belong to `lib/person/`. - `TagInput`, `TagChipList`, `TagParentPicker` — bound to `Tag` entity (D-FE-2), belong to `lib/tag/`. - `EnrichmentBlock`, all `Document*` — bound to `Document` entity. --- ## 7. Dead/suspicious code register | File / Path | Smell | Action | |---|---|---| | `frontend/src/lib/components/OcrProgress.svelte` | Component file is not imported anywhere; only the unrelated `lib/ocr/translateOcrProgress.ts` util is | Delete the .svelte file | | `frontend/src/lib/components/DocumentStatusChip.svelte` | Zero imports across the codebase | Delete | | `frontend/src/lib/components/ExpandableText.svelte` | Zero imports across the codebase | Delete | | `frontend/src/lib/components/DashboardRecentDocuments.svelte` | Only its own `.spec.ts` references it | Delete component + spec | | `frontend/src/lib/index.ts` (line 1) | Empty placeholder: `// place files you want to import …` | Delete | | `frontend/src/lib/utils.ts` (line 1) | 60-byte re-export of `isoToGerman, germanToIso` from `utils/date` — useless redirection | Delete; switch consumers to `$lib/utils/date` | | `frontend/src/lib/components/__mocks__/navigatingStore.ts` (105 bytes) | Test mock buried in production folder | Move to `src/lib/__mocks__/` or co-locate with the consuming spec | | `frontend/src/routes/demo/`, `frontend/src/routes/demo/paraglide/` | `sv create` scaffold leftover, surfaces as `/demo` routes | Delete folder | | `frontend/src/routes/api/tags/+server.ts:22, 27, 32` | Two `console.error` and one `console.log('Tags Data', data)` (dev artifact) | Remove `console.log`; replace `console.error` with proper error handling or accept | | `frontend/src/routes/admin/+layout.server.ts:29-30` | TODO referencing missing backend endpoint `/api/admin/stats` | File a Gitea issue, replace TODO with link | | `frontend/src/lib/components/chronik/ChronikRow.svelte:163` | TODO about backend not exposing comment preview | File a Gitea issue, replace TODO with link | | `frontend/src/routes/users/[id]/+page.svelte` | Semantic overlap with `/admin/users/[id]` (one is Person profile, one is AppUser admin) — confuses Person ≠ AppUser | Decide: rename, merge, or document | | `frontend/.svelte-kit.old/` (24 KB) | Stale generated dir at frontend root; eslint already ignores it (`eslint.config.js:15`) but it's still on disk | Delete | | `frontend/.svelte-kit-backup/` (32 KB) | Same | Delete | | `frontend/test-results.locked/` (20 KB) | Unexplained "locked" copy of test output | Delete | | `frontend/proofshot-artifacts/` (68 KB) | Local browser-verification artifacts; gitignored (`.gitignore` `proofshot-artifacts/`) but present on disk | Delete on commit | | `frontend/src/lib/paraglide_bak/`, `paraglide_bak2/`, `paraglide_bak3/`, `paraglide_bak4/`, `paraglide_bak_b1_chown_pending/`, `paraglide_bak_now/` (≈4 MB total) | 6 backup copies of generated paraglide output; gitignored via `paraglide_bak*` but pollute the source tree and confuse readers | Delete all | | `frontend/src/routes/test-results/screenshots/` | 3 PNGs nested **inside** the route tree; SvelteKit will not route them but they look like a `/test-results` route to a reader | Move to `frontend/test-results/` and gitignore | | `frontend/src/routes/login/test-results/screenshots/` | Same | Move/delete | | `frontend/src/lib/components/test-results/screenshots/` | Same — 3 PNGs in `lib/components/` | Move/delete | | `frontend/README.md:1-39` | Default `# sv` template from `npx sv create` ("If you're seeing this, you've probably already done this step. Congrats!") | Replace with real README | | `frontend/src/lib/types.ts:38` | `DocumentPanelTab = 'metadata' \| 'transcription' \| 'discussion' \| 'history'` and other types are mixed-domain | Split per domain | | `frontend/src/lib/components/PersonMentionEditor.test-host.svelte`, `TranscriptionBlock.test-host.svelte`, `lib/services/confirm.test-host.svelte` | Test hosts living in production component folder | Convention: name them `*.test-host.svelte` and exclude — or move to `__tests__/` | Suspicious-but-not-dead (keep, but note): - `frontend/src/lib/components/StammbaumTree.svelte:2-3` has a file-level `eslint-disable svelte/prefer-svelte-reactivity` — justified inline, OK. - `frontend/src/routes/AppNav.svelte:143-147` has 3 `svelte-ignore` comments for a11y — overlay backdrop click; acceptable but worth a Skip-link audit. --- ## 8. Documentation gaps 1. **No real top-level README in `frontend/`.** The 39-line `# sv` stub at `frontend/README.md` must be replaced. The substance currently lives in `frontend/CLAUDE.md` (198 lines: stack, project structure, API client pattern, form actions, date handling, styling, key components, run instructions, vite proxy, i18n) — every section there belongs in a human-facing README per C5.3. 2. **No per-domain README in `lib/`.** Once REFACTOR-2 creates `lib/<domain>/` directories, each needs a 1-paragraph "what it owns / does NOT own" file. Especially urgent for: `person/` (vs AppUser), `document/transcription/` (vs OCR), `shared/discussion/` (the comment+mention split). 3. **No glossary.** `Person` (historical) vs `AppUser` (login account), `Geschichte` (story), `Briefwechsel` (correspondence), `Chronik` (activity feed), `Stammbaum` (family tree), `Richtlinien` (transcription guidelines), `Aktivitäten` — bilingual terms confuse non-German speakers, even those who can read code. 4. **API contract docs.** `lib/generated/api.ts` (137 KB) is generated from the backend OpenAPI spec; the `frontend/CLAUDE.md:177-183` documents `npm run generate:api`, but the file header in `lib/generated/api.ts` should warn "DO NOT EDIT — regenerate via `npm run generate:api`" and the regen requires backend running with `--spring.profiles.active=dev` (mentioned only in CLAUDE.md). The `.gitignore:30-32` situation (`# src/lib/generated/api.ts` commented out) is confusing. 5. **No env-var reference.** `API_INTERNAL_URL`, `API_PROXY_TARGET`, `E2E_BASE_URL`, `CI` are referenced in `hooks.server.ts:47`, `vite.config.ts:18`, `playwright.config.ts:18`, `playwright.config.ts:23` — never listed together. 6. **No conventions doc** for: how to add a route, how to add an `errors.ts` code, how to wire a new API endpoint into the typed client. Anja and Tobias will have to grep. 7. **CLAUDE.md mentions stale path.** Root `/home/marcel/Desktop/familienarchiv/CLAUDE.md:218` documents `conversations/` route; the actual path is `briefwechsel/`. This is exactly the "click live nav, not URLs from docs" risk previously logged. 8. **No why-comment** for the `DocumentStatus` lifecycle on the frontend side, the `bulkSelection` clear-on-route-change behaviour (good comment in `+layout.svelte:21-27`, but the store itself is uncommented), or the `handleFetch` clone-and-rewrite dance in `hooks.server.ts:91-101`. --- ## 9. Prerequisites for big-bang refactor (REFACTOR-2 / #408) Before any `lib/` reorganisation: 1. **Mutation-validate the test suites that protect refactor candidates.** At minimum: `Person*.svelte.spec.ts`, `Tag*.svelte.spec.ts`, `Document*.svelte.spec.ts`, `Transcription*.svelte.{spec,test}.ts`, `Annotation*.svelte.{spec,test}.ts`, `bulkSelection.svelte.spec.ts`, `notifications.svelte.spec.ts`, `lib/utils/mention.spec.ts`, `lib/utils/blockConflictMerge.spec.ts`, `lib/utils/saveBlockWithConflictRetry.spec.ts`. Use Stryker or hand-mutate one assertion per file as a smoke check. 2. **Resolve the page-local-vs-`lib/` paradigm.** Decide whether components used by exactly one page (the 39+ files currently inside `routes/`) stay co-located or move to `lib/<domain>/`. Without this rule, the refactor will be inconsistent. 3. **Delete the dead code in §7 first.** Refactoring dead files multiplies diff noise. 4. **Settle the boundary** for: `PersonMentionEditor` (person vs shared/discussion), `EnrichmentBlock` (document vs shared/dashboard), `DistributionBar` (shared/ui vs shared/dashboard), `ConversationThumbnail` (document vs conversation derived view), `lib/types.ts` (split or keep). 5. **Confirm path aliases.** All move targets must work with `$lib/<domain>/<File>.svelte`. SvelteKit's `$lib` alias points to `src/lib/` — no extra config needed, but verify `tsconfig.json` and `eslint.config.js` cope with new sub-folders. 6. **Stop new component additions in the flat `lib/components/` bucket** during the refactor window. Add a doc-only rule and a CI check (`grep -E '\$lib/components/[^/]+\.svelte'` should return zero in new diffs after REFACTOR-2 lands). 7. **Settle `/users/[id]` vs `/admin/users/[id]` semantic overlap.** Don't move user-domain components while two user routes still bleed into each other (Person ≠ AppUser). 8. **Add `lib/shared/` with at least one resident** as a baseline before moving everything. --- ## 10. Top 5 prioritized recommendations 1. **Replace `frontend/README.md` with a real human-facing README** built from the contents of `frontend/CLAUDE.md` (and add Anja-readable sections: domains, env vars, glossary entries, "how to add an X"). This single change resolves three Critical findings (C1.1, C2.5, C3.1) and unblocks everything else. Also write a top-level repo `README.md` per AUDIT-5 scope. 2. **Carve `lib/components/` into `lib/<domain>/{component,store,util,hook,type}` per the canonical set** (REFACTOR-2). Until this happens, any "find me the Person code" question fails. Use the §5 mapping verbatim; create `lib/shared/{ui,discussion,dashboard,i18n}` as the cross-cutting bucket. 3. **Run a one-day mutation-test pass on the 14 spec files that protect refactor candidates** (listed in §9.1) and only then start moving files. This satisfies the C8.1 deferral and keeps REFACTOR-2 honest. 4. **Delete every item in §7 in a single "tree-clean" PR before REFACTOR-2 starts.** ~4 MB of `paraglide_bak*`, the 4 dead components, `frontend/src/lib/index.ts`, `frontend/src/lib/utils.ts`, the 3 stray `test-results/screenshots/` dirs inside `src/`, the `frontend/.svelte-kit.old/`, `routes/demo/`, the stray `console.log`, the two stale TODOs (filed as Gitea issues). Reviewing a refactor diff is much harder when half the moved files are dead. 5. **Add `docs/GLOSSARY.md` with the bilingual + Person ≠ AppUser distinctions** and link it from both root `README.md` and `frontend/README.md`. Tobias will not get past `Briefwechsel`, `Chronik`, `Stammbaum`, `Richtlinien`, `Aktivitäten`, `Geschichte` without it; Anja will not get past `Person` ≠ `AppUser` without it. Cheap, high-leverage, unblocks C3.3 (Critical). --- *Report generated under AUDIT-1, scope per #388, references #387 first comment. Read-only audit; no code modified.*
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#388