Restructure the "New Document" page so users can save quickly:
- FileSectionNew becomes the first element, redesigned as a prominent
upload zone with an icon and large click target
- Title field is rendered standalone below the upload zone; it
auto-populates from the filename (via parseFilename + stripExtension
fallback) unless the user has already typed something
- All remaining metadata (who/when, description, transcription) moves
into a collapsible "Weitere Details" section that auto-expands when
URL prefill data or a form error is present, or when filename parsing
detects a date/person
- title is no longer required — the form can be saved with only a file
- DescriptionSection gains a `hideTitle` prop for use in this layout
- `form_label_title` translation key no longer carries a hardcoded `*`;
the asterisk is rendered by the template only when `titleRequired` is
set (currently only the edit form)
- E2E tests added for all three scenarios from the issue
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In dark mode --c-primary flips to mint (#a1dcd8), making text-white
unreadable. text-primary-fg is already paired correctly in both modes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show the discussion count badge on every state (including 0) instead of
a separate nudge button. Simpler, less intrusive, and works without
needing an extra element near the panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add comment count badge on the Discussion tab (seeded from SSR, updated live)
- Add 'Diskussion starten' nudge above collapsed panel when no comments exist
- Add empty state hint with speech-bubble icon inside the discussion panel
- Fix CommentThread to fire onCountChange with SSR-seeded count on mount
- Add tests for all three behaviours in CommentThread and DocumentBottomPanel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the four fixed regexes with a split-based algorithm:
- first segment = date → last segment = firstName, rest = lastName parts
- last segment = date → second-to-last = firstName, rest = lastName parts
18881025_de_Gruyter_Walter.pdf now correctly yields "Walter de Gruyter".
Simple two-segment names behave identically to before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a file is selected on the new document page, parseFilename runs
on the filename and suggests date, sender name and title via the new
suggestedDateIso / suggestedSenderName / suggestedTitle props. Each
suggestion is applied only while the respective field is still clean
(not dirty), so manual input is never overwritten.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Supports four patterns: date_lastname_firstname and lastname_firstname_date,
both with ISO (YYYY-MM-DD) and compact (YYYYMMDD) date formats.
Returns dateIso, personName and a formatted suggestedTitle.
Partial matches are rejected — unrecognised filenames return {}.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Home page shows "Needs metadata" card when incomplete documents exist.
/enrich list shows all incomplete documents; /enrich/[id] provides a
split PDF-preview + compact form view with Skip / Save / Save & reviewed
actions that auto-advance through the queue.
New document page gets Save vs Save & reviewed split. Edit page gets
"Mark for review" secondary button to push a document back into the queue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
- Switch errors from plain strings to { filename, code } objects so the
frontend can show translated messages instead of raw exception text
- Add UNSUPPORTED_FILE_TYPE error code end-to-end (Java enum → errors.ts
→ de/en/es messages)
- Fix IncorrectResultSizeDataAccessException when a filename exists more
than once in the DB: use findFirstByOriginalFilename instead of
findByOriginalFilename in storeDocument()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PdfViewer: add $effect that forces showAnnotations=true when annotateMode
becomes true, so hiding annotations before drawing no longer breaks drawing
- DocumentViewer: restore missing fileHash field on Doc type and pass
documentFileHash to PdfViewer (lost when rebase dropped the merge commit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace text-gray-*, bg-gray-*, border-gray-*, divide-gray-*, placeholder-gray-*,
focus:border-blue-*, focus:ring-blue-*, hover:bg-blue-*, and ring-brand-mint with
their semantic-token equivalents (text-ink, bg-muted, border-line, etc.) across
all pages and shared components so dark mode renders correctly everywhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces bg-white, text-brand-navy, border-brand-sand, text-gray-*, bg-[#2A2A2A],
bg-brand-purple/15, hover:bg-brand-sand, etc. across all 35 .svelte files with
semantic token utilities (bg-surface, text-ink, border-line, bg-pdf-bg, bg-nav-active,
bg-muted, text-accent, bg-primary, ...).
Also adds CSS filter: invert(1) in layout.css for De Gruyter <img> icons in dark mode,
excluding icons that carry .invert already (to prevent double-inversion).
Closes#64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline <script> in app.html applies saved localStorage theme before first
paint to prevent flash of wrong theme
- ThemeToggle.svelte: moon/sun button, localStorage persistence, sets
data-theme on <html>, defaults to system preference on first visit
- Placed in +layout.svelte between language selector and user menu
- E2E tests cover visibility, toggle, reverse toggle, persistence, and
no-flash behaviour — all 6 passing
Refs #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No logic changes — whitespace and indentation only. These were flagged
by the pre-commit hook when running lint after layout.css was modified.
Refs #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep the new bottom-panel / AnnotationSidePanel architecture from this branch
while pulling in the documentFileHash / visibleAnnotations filter that was added
on main. Thread documentFileHash through DocumentViewer so outdated-annotation
filtering works end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pointer-events-none and pointer-events-auto were both present as static
and conditional Tailwind classes simultaneously. CSS specificity meant
pointer-events-none always won, so clicks passed through to the
annotation toggle button behind the panel. Now pointer-events-none is
only applied when the panel is hidden (translated off-screen).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Annotation threads now open in a slide-in side panel (320 px, right
edge of the PDF viewer) instead of expanding the bottom drawer.
The PDF stays visible while the user reads and writes annotation
comments.
- Add AnnotationSidePanel component (absolute-positioned, CSS slide
transition, keyed CommentThread, close via X or Escape)
- Remove the $effect that opened the bottom drawer on annotation click
- Simplify PanelDiscussion back to document-level thread only (no
annotation sub-tabs)
- Remove annotation-related props from DocumentBottomPanel and
PanelDiscussion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The document viewer container was using fixed inset-0 z-50 which
covered the sticky global nav bar. Now measures nav height at mount
and offsets the container top accordingly, dropping z-index to z-40.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of rendering the diff at the bottom of the list (requiring the user
to scroll down), it now appears directly below whichever version item was
clicked. Compare-mode diff stays at the bottom of the compare form where it
makes sense, since it is not tied to a specific list item.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of an arbitrary 80 % cap, the panel now measures the actual
DocumentTopBar height at open time and fills the remaining viewport
exactly — so the PDF is fully covered and the drawer reaches right up
to the header. Drag-to-shrink still works as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Panel now opens to 80 % of the viewport height so the user can immediately
read comments and metadata without having to drag it up first.
The user can drag the top handle down to make it smaller; that size is
persisted to localStorage and restored on the next visit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Button was rendered outside the controls bar (below the toolbar). Moved it
inside so it stays in the same row as zoom and page controls. Added a text
label next to the eye icon so the action is self-descriptive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eye/eye-slash button in the PDF controls bar lets the user hide all
annotation highlights to read the document unobstructed and show them again
with one click.
- Button only renders when at least one annotation exists
- Active state (hidden) highlighted with brand-mint/bg-white/10 so the
current state is always clear
- i18n keys added for de/en/es
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking the Diskussion sub-tab no longer deselects the active annotation,
so the Annotation tab stays visible and accessible for easy toggling back.
The annotation is cleared only via Escape or clicking elsewhere on the PDF.
Removes the now-unused onClearAnnotation callback chain.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Within the Diskussion panel tab, show two sub-tabs when an annotation is
active: «Diskussion» (document-level thread, with comment-count badge) and
«Annotation · Seite N» (annotation-specific thread).
Behaviour:
- Clicking an annotation auto-switches to the Annotation sub-tab
- Clicking the Diskussion sub-tab deselects the annotation and returns to
the document thread
- Escape clears the active annotation (or collapses the panel if none)
- activeAnnotationPage is now lifted from PdfViewer → DocumentViewer →
page → DocumentBottomPanel → PanelDiscussion so the tab label shows the
correct page number
Closes#60
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the left sidebar layout with:
- Full-viewport PDF/image viewer (never resizes, position: absolute)
- Fixed floating bottom panel with tabs: Metadaten, Transkription,
Diskussion, Verlauf
- Compact top bar with title, date · sender → receivers row, and
Annotieren / Edit / Download actions
- Drag-to-resize panel with localStorage persistence of open/height/tab
- Panel opens automatically to Diskussion when an annotation is clicked
- Documents without a file default to showing the Metadaten tab
New components: DocumentTopBar, DocumentViewer, DocumentBottomPanel,
PanelMetadata, PanelTranscription, PanelDiscussion, PanelHistory
PdfViewer: annotateMode and activeAnnotationId lifted to bindable props;
AnnotationCommentPanel removed (discussion moves to the Diskussion tab).
Closes#62
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Regenerate API types with fileHash on Document and DocumentAnnotation
- PdfViewer accepts documentFileHash prop; filters visibleAnnotations to
those whose hash matches (or is null) and shows an amber notice banner
when any annotations are hidden due to a hash mismatch
- Document detail page passes doc.fileHash to PdfViewer
- Add i18n key annotation_outdated_notice in de/en/es
- E2E: two new tests covering hide-on-reupload and restore-on-original-reupload
scenarios; add minimal2.pdf fixture for a different-hash upload
Closes#55
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add aria-label="Kommentare anzeigen" to annotation container div so
getByRole('button', { name: /annotation löschen/i }) no longer
matches the container (its name was previously inherited from the
child delete button, causing the test to click the wrong element)
- Wrap the server-side comments fetch in a .catch and try/catch so a
network error or non-JSON response never crashes the document load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the panel in {#key activeAnnotationId} so Svelte destroys and
recreates it on every annotation change, triggering onMount and
loading the correct comments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show a native confirm() dialog when the annotation has ≥1 comment,
listing the count so the user knows what will be lost.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-open AnnotationCommentPanel immediately after drawing a new annotation
- Move comment count pill to bottom-right corner (was centered at bottom)
- Increase pill size: font 11px bold, padding 2px 6px, min-width 20px, drop shadow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace opacity: 0.3 on the annotation container with an rgba
background so child elements (the × button) are not affected by
the parent's opacity and render at full opacity.
Refs #40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a child element inside an annotation div (e.g. the delete button)
was clicked, the AnnotationLayer's pointerdown handler would call
setPointerCapture, preventing the child's click event from firing.
Using closest('[data-annotation]') instead of checking dataset.annotation
on the target directly fixes delete buttons inside annotation elements.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scale was only read inside the async renderPage function, so Svelte 5
never tracked it as a reactive dependency of the effect. Reading scale
synchronously in the effect condition registers it as a dependency and
triggers a re-render on every zoom change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static import of pdfjs-dist fails during SSR because DOMMatrix and
other browser globals are unavailable in Node.js. Move the import into
onMount so it only ever executes in the browser. A plain pdfjsLib
variable holds the module; a $state boolean pdfjsReady triggers the
load-document effect once the library is available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install pdfjs-dist v5 and add optimizeDeps pre-bundle config
- New PdfViewer.svelte component: renders each page on a <canvas> with
correct device-pixel-ratio scaling, overlays a text layer (enables
text selection; foundation for annotations in #40), prev/next
navigation, zoom controls, and lazy page rendering (only current ±1
pre-fetched — avoids freezing on multi-page documents)
- Replace the <iframe> in documents/[id]/+page.svelte with PdfViewer;
image attachments continue to use <img>; detection now uses
doc.contentType instead of filename extension
- Unit tests for navigation controls and page counter (pdfjs mocked)
- E2E tests: PDF renders as canvas (not iframe), nav controls visible,
image fallback stays as <img>; minimal.pdf fixture for upload tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ExpandableText.svelte which clamps text to 10 lines and shows a
toggle button only when the content actually overflows. Applied to the
summary and transcription fields on the document detail page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin can trigger an initial history snapshot for all documents without
version history. Shows count of backfilled documents after completion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a collapsible history section to the document detail view, showing
all saved versions with changed-field labels, word-level diff between
adjacent versions, and a compare mode for any two arbitrary versions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /forgot-password: email form → sends POST /api/auth/forgot-password → success banner
- /reset-password: password form reads token from URL → sends POST /api/auth/reset-password
- Login page: add "Passwort vergessen?" link
- hooks.server.ts: add /forgot-password and /reset-password to PUBLIC_PATHS; skip auth
injection for public auth API endpoints
- errors.ts: add INVALID_RESET_TOKEN error code
- i18n: add all new message keys in de/en/es
- playwright.config.ts: use E2E_BASE_URL for webServer check URL (allows reusing docker
dev server at port 5173 locally)
- ci.yml: pass E2E_BACKEND_URL=http://localhost:8080 to E2E test step
- e2e/password-reset.spec.ts: 5 tests (4 pass locally, full flow requires e2e profile in CI)
- Regenerated OpenAPI types including new /api/auth/* endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>