refactor(frontend): split large page components into focused sub-components (#75) #76

Merged
marcel merged 11 commits from feat/75-split-page-components into main 2026-03-26 13:01:37 +01:00
Owner

Summary

Closes #75

Purely structural refactor — no behavior changes. Each touched +page.svelte now reads as a clear composition of named sub-components, co-located with their route.

Page splits

  • refactor(utils) — Consolidate date utilities into $lib/utils/date.ts (isoToGerman, germanToIso, handleGermanDateInput); $lib/utils.ts becomes a re-export shim
  • refactor(layout) — Extract AppNav (logo + nav links) and UserMenu (avatar dropdown) from +layout.svelte (205 → ~80 lines)
  • refactor(admin) — Extract UsersTab, TagsTab, GroupsTab, SystemTab from admin page (573 → ~40 lines)
  • refactor(profile) — Extract PersonalInfoForm and PasswordChangeForm (240 → ~25 lines)
  • refactor(admin/users) — Extract shared UserProfileSection, UserGroupsSection, UserPasswordSection (to $lib/components/user/) and page-local AccountSection; used by both admin/users/[id] and admin/users/new
  • refactor(documents) — Extract shared WhoWhenSection, DescriptionSection, TranscriptionSection (to $lib/components/document/) and route-local FileSectionEdit, FileSectionNew, SaveBar; used by both edit and new pages
  • refactor(conversations) — Extract ConversationFilterBar and ConversationTimeline (346 → ~70 lines)
  • refactor(home) — Extract SearchFilterBar, DropZone, DocumentList (580 → ~60 lines)
  • refactor(persons) — Extract PersonCard, PersonMergePanel, CoCorrespondentsList, PersonDocumentList (610 → ~55 lines)

Follow-up cleanup

  • refactor(comments) — Eliminate duplicated root/reply markup in CommentThread using a {#snippet commentEntry(...)} (first use of Svelte 5 snippets in the codebase)
  • refactor(types) — Extract shared types to $lib/types.ts: Comment + CommentReply (were in 3 files), DocumentPanelTab (2 files), Annotation (2 files, PdfViewer's fileHash? is now canonical)

Design decisions

  • Shared components ($lib/components/) render field content only — no card wrappers. Pages add card wrappers as needed (avoids conflict between single-card and multi-card layouts)
  • SaveBar uses HTML5 form="delete-form" attribute to submit the parent's delete form without needing its own <form> or use:enhance
  • PersonMergePanel state resets automatically via {#key person.id} in the parent (no manual $effect needed)
  • PersonDocumentList is reused for both sent and received document lists (heading + emptyMessage passed as props)
  • Doc partial types intentionally left as-is per component — each component narrows to only the fields it needs

Test plan

  • npm run check passes (only pre-existing errors in admin/users/new/+page.server.ts)
  • npm run lint passes (0 errors)
  • Home page: search, filter, drag-and-drop upload all work
  • Document create and edit forms save correctly
  • Conversations filter bar and timeline render correctly
  • Person detail page: view/edit, merge, correspondent chips, document lists all work
  • Admin tabs: users, tags, groups, system backfill all work
  • Profile page: personal info and password change save correctly
  • Comment thread: post, reply, edit, delete all work

🤖 Generated with Claude Code

## Summary Closes #75 Purely structural refactor — no behavior changes. Each touched `+page.svelte` now reads as a clear composition of named sub-components, co-located with their route. ### Page splits - **`refactor(utils)`** — Consolidate date utilities into `$lib/utils/date.ts` (`isoToGerman`, `germanToIso`, `handleGermanDateInput`); `$lib/utils.ts` becomes a re-export shim - **`refactor(layout)`** — Extract `AppNav` (logo + nav links) and `UserMenu` (avatar dropdown) from `+layout.svelte` (205 → ~80 lines) - **`refactor(admin)`** — Extract `UsersTab`, `TagsTab`, `GroupsTab`, `SystemTab` from admin page (573 → ~40 lines) - **`refactor(profile)`** — Extract `PersonalInfoForm` and `PasswordChangeForm` (240 → ~25 lines) - **`refactor(admin/users)`** — Extract shared `UserProfileSection`, `UserGroupsSection`, `UserPasswordSection` (to `$lib/components/user/`) and page-local `AccountSection`; used by both `admin/users/[id]` and `admin/users/new` - **`refactor(documents)`** — Extract shared `WhoWhenSection`, `DescriptionSection`, `TranscriptionSection` (to `$lib/components/document/`) and route-local `FileSectionEdit`, `FileSectionNew`, `SaveBar`; used by both `edit` and `new` pages - **`refactor(conversations)`** — Extract `ConversationFilterBar` and `ConversationTimeline` (346 → ~70 lines) - **`refactor(home)`** — Extract `SearchFilterBar`, `DropZone`, `DocumentList` (580 → ~60 lines) - **`refactor(persons)`** — Extract `PersonCard`, `PersonMergePanel`, `CoCorrespondentsList`, `PersonDocumentList` (610 → ~55 lines) ### Follow-up cleanup - **`refactor(comments)`** — Eliminate duplicated root/reply markup in `CommentThread` using a `{#snippet commentEntry(...)}` (first use of Svelte 5 snippets in the codebase) - **`refactor(types)`** — Extract shared types to `$lib/types.ts`: `Comment` + `CommentReply` (were in 3 files), `DocumentPanelTab` (2 files), `Annotation` (2 files, PdfViewer's `fileHash?` is now canonical) ### Design decisions - **Shared components** (`$lib/components/`) render field content only — no card wrappers. Pages add card wrappers as needed (avoids conflict between single-card and multi-card layouts) - **`SaveBar`** uses HTML5 `form="delete-form"` attribute to submit the parent's delete form without needing its own `<form>` or `use:enhance` - **`PersonMergePanel`** state resets automatically via `{#key person.id}` in the parent (no manual `$effect` needed) - **`PersonDocumentList`** is reused for both sent and received document lists (heading + emptyMessage passed as props) - **`Doc` partial types** intentionally left as-is per component — each component narrows to only the fields it needs ## Test plan - [ ] `npm run check` passes (only pre-existing errors in `admin/users/new/+page.server.ts`) - [ ] `npm run lint` passes (0 errors) - [ ] Home page: search, filter, drag-and-drop upload all work - [ ] Document create and edit forms save correctly - [ ] Conversations filter bar and timeline render correctly - [ ] Person detail page: view/edit, merge, correspondent chips, document lists all work - [ ] Admin tabs: users, tags, groups, system backfill all work - [ ] Profile page: personal info and password change save correctly - [ ] Comment thread: post, reply, edit, delete all work 🤖 Generated with [Claude Code](https://claude.com/claude-code)
marcel added 9 commits 2026-03-26 12:34:22 +01:00
Move isoToGerman and germanToIso from utils.ts into utils/date.ts alongside
formatDate, and add handleGermanDateInput for the shared date field handler.
Make utils.ts a re-export shim so existing imports continue to work.

Closes part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split +layout.svelte (205 lines) into:
- AppNav.svelte: logo + nav links with active-state styling
- UserMenu.svelte: avatar button, dropdown, click-outside handler

Layout drops from 205 → 80 lines.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split admin/+page.svelte (573 lines) into:
- UsersTab.svelte: user table with delete action
- TagsTab.svelte: tag list with inline rename and delete
- GroupsTab.svelte: groups table with inline edit + create form
- SystemTab.svelte: backfill buttons with own state

Page drops from 573 → ~40 lines.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split profile/+page.svelte (240 lines) into:
- PersonalInfoForm.svelte: name/birth-date/email/contact with own date state
- PasswordChangeForm.svelte: current/new/confirm password fields

Page drops from 240 → ~25 lines.
Date utilities now imported from \$lib/utils/date instead of duplicated inline.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shared (src/lib/components/user/):
- UserProfileSection.svelte: name/birth-date/email/contact fields
- UserGroupsSection.svelte: group checkboxes
- UserPasswordSection.svelte: new/confirm password fields

Page-local:
- admin/users/new/AccountSection.svelte: username + initial password

admin/users/[id] drops from 224 → ~35 lines.
admin/users/new drops from 191 → ~30 lines.
Date utilities imported from \$lib/utils/date.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shared (src/lib/components/document/):
- WhoWhenSection.svelte: date/location/sender/receivers; owns date state
- DescriptionSection.svelte: title/archive-loc/tags/summary; owns tag binding
- TranscriptionSection.svelte: transcription textarea

Page-local:
- documents/[id]/edit/FileSectionEdit.svelte: current file + replace input
- documents/[id]/edit/SaveBar.svelte: sticky bar with two-step delete confirm
- documents/new/FileSectionNew.svelte: initial file upload input

documents/[id]/edit drops from 319 → ~40 lines.
documents/new drops from 254 → ~30 lines.
Date handling imported from \$lib/utils/date.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split conversations/+page.svelte (346 lines) into:
- ConversationFilterBar.svelte: person A/B typeaheads, swap button, date range, sort toggle
- ConversationTimeline.svelte: summary bar, chat bubbles, year dividers, new-doc link

Page drops from 346 → ~70 lines; navigation logic and filter state stay in the page.

Part of #75

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the 580-line home page into three focused co-located components:
- SearchFilterBar: full-text search + collapsible advanced filters
- DropZone: drag-and-drop / click-to-upload with progress and messages
- DocumentList: document list with new-doc link and empty state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(persons): extract PersonCard, PersonMergePanel, CoCorrespondentsList, PersonDocumentList
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
0db68da00c
Split the 610-line person detail page into four focused co-located components:
- PersonCard: view/edit card with inline form (owns editMode)
- PersonMergePanel: merge target typeahead + two-step confirm (state reset via {#key})
- CoCorrespondentsList: frequency-ranked correspondent chips linking to conversations
- PersonDocumentList: reusable sorted/paginated document list (used for sent + received)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel added 2 commits 2026-03-26 12:56:30 +01:00
The root-comment and reply rendering blocks were near-identical (view mode
with author/time/edit-delete, and edit mode with textarea/save/cancel).
Extracted a local {#snippet commentEntry(comment, threadId, showReplyButton)}
that handles both states, introducing Svelte 5 snippets to the codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(types): extract shared types to \$lib/types.ts
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 2m22s
CI / Backend Unit Tests (pull_request) Successful in 2m16s
CI / E2E Tests (pull_request) Failing after 31m23s
CI / Unit & Component Tests (push) Successful in 2m24s
CI / Backend Unit Tests (push) Successful in 2m6s
CI / E2E Tests (push) Failing after 30m19s
2bfbf45eba
Eliminates type duplication across 6 files by introducing a single
shared types module:

- Comment + CommentReply: were identically defined in CommentThread,
  PanelDiscussion, and DocumentBottomPanel
- DocumentPanelTab: was identically defined in DocumentBottomPanel
  and documents/[id]/+page.svelte
- Annotation: was defined in both AnnotationLayer and PdfViewer
  (PdfViewer's variant with fileHash? is now the canonical definition)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel merged commit 2bfbf45eba into main 2026-03-26 13:01:37 +01:00
marcel deleted branch feat/75-split-page-components 2026-03-26 13:01:38 +01:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#76