Commit Graph

295 Commits

Author SHA1 Message Date
Marcel
8acae8ea4d refactor(frontend): extract shared TrainingRun type to $lib/types/training.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 12:30:54 +02:00
Marcel
a296ad527e feat(ocr): wire SenderModelService into OcrAsyncRunner; stage missing foundational files
OcrAsyncRunner now passes the per-sender model path to streamBlocks for
HANDWRITING_KURRENT documents. processDocument replaced extractBlocks
with streamBlocks + AtomicReference, removing the unchecked raw-array
pattern.

Also stages all previously uncommitted foundational files for this
feature: SenderModel entity, SenderModelRepository, Flyway migrations
V40/V41, updated OcrClient/RestClientOcrClient streaming API,
TrainingDataExportService.exportForSender, TranscriptionService Kurrent
hook, application.yaml OCR config, and frontend i18n/test additions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 12:30:54 +02:00
Marcel
cd31bf63c1 feat(frontend): wire personNames to TrainingHistory in OcrTrainingCard
Extends Run interface with personId and QUEUED status, TrainingInfo with
personNames map, and passes it through to TrainingHistory for per-sender
model column display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 12:30:54 +02:00
Marcel
bf010a23c3 docs(tag-input): add clarifying comments for non-obvious design decisions
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m26s
CI / Backend Unit Tests (pull_request) Failing after 2m44s
CI / Unit & Component Tests (push) Failing after 2m28s
CI / Backend Unit Tests (push) Failing after 25s
- SvelteMap satisfies svelte/prefer-svelte-reactivity; $derived.by() handles reactivity
- ‹›› prefix only on depth=0 context ancestors; indentation serves deeper nodes
- fetchedForQuery set after suggestions causes harmless double $derived evaluation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:17:22 +02:00
Marcel
b01761d800 test(tag-input): add regression guard for allowCreation=false + Enter on suggestion
Confirms that Enter on a suggestion item adds the tag even when allowCreation is
false — the activeIndex guard in handleKeydown runs before the allowCreation check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:14:35 +02:00
Marcel
6b7829d5c8 test(tag-input): rename waitForDebounce to waitForFetch and reduce to 50ms
fetchSuggestions has no debounce; the wait is purely for the async mock to
resolve. The old name implied semantics that don't exist and added ~4.5s to
the suite (13 uses × 350ms).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:11:54 +02:00
Marcel
1b617aa08b fix(tag-input): increase suggestion item padding to py-3 for 44px touch target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:09:20 +02:00
Marcel
5120dd19a1 feat(tag-input): tree-aware DFS ordering, depth indentation, and direct-match styling
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / Backend Unit Tests (push) Failing after 2m39s
CI / Unit & Component Tests (pull_request) Failing after 2m28s
CI / Backend Unit Tests (pull_request) Failing after 2m41s
Rewrites orderedSuggestions to a recursive DFS with SuggestionEntry type,
adds role=listbox, depth indentation via inline style, font-medium for direct
matches, text-ink-3 for context nodes, and › prefix for root-level ancestors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:54:28 +02:00
Marcel
b5ea04e47a feat(#248): add optional placeholder prop to TagParentPicker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 08:04:16 +02:00
Marcel
3cd6483042 fix(#248): replace focus:outline-none with focus-visible ring on TagParentPicker clear button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:43:38 +02:00
Marcel
aff7afa7cb fix(#248): resolve parent UUID to name in TagParentPicker dropdown subtitle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:42:13 +02:00
Marcel
901483ab73 fix(#248): complete ARIA combobox pattern in TagParentPicker — role="option", aria-activedescendant, keyboard nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 00:42:54 +02:00
Marcel
7919ba3a57 fix(#248): address PR review concerns — i18n, aria-label, stable keys, test selectors
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / Backend Unit Tests (push) Failing after 2m48s
CI / Unit & Component Tests (pull_request) Failing after 2m35s
CI / Backend Unit Tests (pull_request) Failing after 2m49s
- Add filter_operator_and/or/and_label/or_label i18n keys to de/en/es locale files
- Add aria-label and aria-pressed to AND/OR toggle buttons in SearchFilterBar
- Add data-testid="operator-and/or" for unambiguous test targeting (fixes substring match on German "Schlagwort")
- Use stable keys (tag.id ?? tag.name) for TagInput chip and suggestion lists
- Remove aria-level from role="option" items in TagInput (invalid attribute for that role)
- Add aria-live="polite" role="status" to TagMergeZone step indicator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 00:24:53 +02:00
Marcel
a3660a79e1 feat(#248): add TagParentPicker combobox component with excludeIds filtering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 23:16:18 +02:00
Marcel
53d89a44fc refactor(#248): extract typeahead logic into createTypeahead composable, use in PersonTypeahead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 23:07:59 +02:00
Marcel
e8e54cc282 feat(#221): change TagInput binding to Tag[], add color dots and hierarchy grouping
Backend:
- TagRepository: add findDescendantIdsByName() recursive CTE query
- TagService: add expandTagNamesToDescendantIdSets() for document search

Frontend:
- TagInput: accept Tag[] (id, name, color, parentId) instead of string[]
- Chips show color dot via var(--c-tag-{color}) when tag has color
- Suggestions grouped hierarchically: children indented under their parents
- Update DescriptionSection, edit/new pages, SearchFilterBar, +page.svelte

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:11:38 +02:00
Marcel
b0c6d15f99 fix(#240): rename transcription column heading to "Text transkribieren"
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m26s
CI / Backend Unit Tests (pull_request) Failing after 2m41s
CI / Unit & Component Tests (push) Failing after 2m26s
CI / Backend Unit Tests (push) Failing after 2m41s
"Text eintippen" sounded too casual and diverged from the domain
language used elsewhere in the app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:37:46 +02:00
Marcel
e808525312 fix(#240): rename segmentation column heading to "Text markieren"
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m30s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / Backend Unit Tests (pull_request) Failing after 2m40s
"Rahmen einzeichnen" assumed familiarity with the segmentation concept;
"Text markieren" is self-explanatory for new contributors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:35:56 +02:00
Marcel
6c2da648db fix(#240): remove weekly pulse badge from ReadyColumn
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m32s
CI / Backend Unit Tests (push) Failing after 2m45s
CI / Unit & Component Tests (pull_request) Failing after 2m27s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
The weekly count in Lesefertig counted any document with a reviewed
block in the past 7 days, not documents that crossed the ≥90% ready
threshold — a misleading stat given the column shows a different set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:12:46 +02:00
Marcel
ca660f103d test(#240): add component tests for all four Mission Control Strip components
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m29s
CI / Backend Unit Tests (pull_request) Failing after 2m37s
CI / Unit & Component Tests (push) Failing after 2m21s
CI / Backend Unit Tests (push) Failing after 2m38s
17 tests across SegmentationColumn, TranscriptionColumn, ReadyColumn,
MissionControlStrip. Covers document list rendering, per-column empty
states, weekly pulse visibility, link hrefs, progress bar, and the
reviewedPct denominator (annotationCount, not textedBlockCount).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:36:33 +02:00
Marcel
06eb1cada8 refactor(#240): deduplicate formatDate, use generated types, always-visible strip
- Add formatMCDate() to $lib/utils/date.ts (locale-aware, medium format);
  remove duplicated inline formatDate() from all three column components
- Replace local TranscriptionQueueItemDTO/TranscriptionWeeklyStatsDTO type
  declarations with imports from $lib/generated/api across all four components
- Add dashed empty states to SegmentationColumn and TranscriptionColumn
  (ReadyColumn already had one)
- Remove outer {#if} from MissionControlStrip so the section is always
  visible — each column owns its own empty state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:28:20 +02:00
Marcel
d78685c5a4 fix(#240): accessibility, color consistency, and reviewedPct denominator
- TranscriptionColumn progress bar: add aria-hidden="true" (the block count
  text above already communicates the value to screen readers)
- TranscriptionColumn weekly pulse: text-ink → text-ink-2 (matches
  SegmentationColumn, same semantic element)
- ReadyColumn reviewedPct: align denominator to annotationCount so the
  displayed percentage matches the SQL threshold used to classify "ready"
- page.svelte.spec.ts: add missing segmentationDocs/transcriptionDocs/
  readyDocs/weeklyStats to emptyData fixture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:25:36 +02:00
Marcel
ca0cf4903c refactor(#240): remove needsExpert feature completely
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m23s
CI / Backend Unit Tests (pull_request) Failing after 2m43s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
Drops the needsExpert / needs_expert flag end-to-end: DB migration
(V37, never applied), Document entity field, PATCH endpoint, service
method, DTO field, all three queue queries, ExpertBadge component,
i18n key, generated API types, and test fixture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:52:14 +02:00
Marcel
9fb1821db5 fix(#240): remove CTA buttons and dead i18n keys from Mission Control Strip
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m29s
CI / Backend Unit Tests (pull_request) Failing after 2m41s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
The enrich page already handles task routing; the buttons in the
segmentation and transcription columns were redundant. Removes the
unused mission_control_segmentation_cta, mission_control_transcription_cta,
and mission_control_ready_all_cta keys from all three locale files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
86a216918f fix(#240): make Mission Control Strip dark-mode compatible
Replace all hardcoded Tailwind colours with semantic tokens:
- bg-white → bg-surface (outer strip container)
- text-gray-400 → text-ink-3 (dates, meta text, empty-state copy)
- text-green-800 / text-green-700 → text-ink / text-ink-2 (headings, pulse, reviewed %)
- bg-green-50 / border-green-200 → bg-accent-bg / border-line (skill pill, weekly pulse badge)
- bg-ink text-white → bg-primary text-primary-fg (CTA buttons; dark: mint bg + navy text)
- hover:text-white → hover:text-primary-fg (ghost CTA hover text)
- focus-visible:ring-brand-navy → focus-visible:ring-focus-ring (all doc links)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
48152517aa fix(#240): fix invisible hover on column 1 & 2 doc links
brand-sand/30 on white background is near-invisible; use full
hover:bg-brand-sand instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
4af2e4ad17 fix(#240): remove dead "Alle lesen" link and add hover shadow to ReadyColumn
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
94b5d1a5a8 fix(#240): align Mission Control Strip UI with final spec
- Strip heading: "Mitarbeiten" → "Was braucht Aufmerksamkeit?"
- Column 1 heading: "Segmentierung" → "Rahmen einzeichnen"; add green
  skill pill "✓ Ohne Vorkenntnisse"; heading color gray → ink (navy)
- Column 2 heading: "Transkription" → "Text eintippen"; add navy skill
  pill "Kurrent hilfreich"; heading color gray → ink; weekly pulse
  color green → ink (task, not achievement); progress bar track
  bg-gray-200/h-1.5 → bg-ink/20/h-1; add transition-all to fill
- Column 3 heading: "Lesefertig" → "Lesefertig ✓"; heading color
  gray → green-800; add "N Dokumente bereit" subtitle in green; add
  "Alle N lesen →" link at bottom; reviewed % color gray → green-800
- All columns: add CTA buttons at bottom (Jetzt einzeichnen /
  Jetzt tippen); empty state removed from cols 1 & 2 (columns
  hide when empty); empty-state ghost CTA in col 3 restyled as
  bordered button with hover:bg-ink
- Strip: add visibility guard — hides when all three lists are empty
- i18n: add mission_control_seg_skill_pill, mission_control_trans_skill_pill,
  mission_control_ready_subtitle, mission_control_ready_all_cta in
  de/en/es; update heading and CTA copy in all three locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
aa8fb70d10 fix(#240): redirect Mission Control Strip links to document detail page
The /enrich route is for metadata (title, date, sender/receiver).
Segmentation and transcription work happens on the document detail page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:18 +02:00
Marcel
f36bebd1a8 feat(#240): Mission Control Strip frontend — 5 components + dashboard wiring
Adds the full-width 3-column collaboration widget below the existing
dashboard grid. Renders without the backend running (Promise.allSettled
isolation keeps failures silent).

Components (src/lib/components/):
- ExpertBadge.svelte — purple pill with icon, no props
- SegmentationColumn.svelte — col 1: links to /enrich/{id}, weekly pulse
- TranscriptionColumn.svelte — col 2: per-doc progress bar when blocks exist
- ReadyColumn.svelte — col 3: mint border when filled, dashed empty state
- MissionControlStrip.svelte — strip wrapper, 1-col mobile / 3-col sm+

i18n: 19 new keys added to de/en/es (mission_control_*)

Page wiring:
- +page.server.ts: 4 new Promise.allSettled calls for segmentation-queue,
  transcription-queue, ready-to-read, weekly-stats; all failures silent
- +page.svelte: MissionControlStrip rendered below the grid in isDashboard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:42:07 +02:00
Marcel
fabab6b502 fix(pdf): merge setElements and render effects so canvas remount triggers re-render
The refactor made pdfDoc a plain variable so renderer.isLoaded was not
reactive. Svelte only tracked currentPage and scale — but when the canvas
reappeared after loading, neither changed, so the PDF stayed blank.

Fix: merge the two effects into one that reads canvasEl synchronously.
Svelte now tracks canvasEl as a dependency; when the canvas remounts
(loading spinner → false), the effect re-fires and renders the
already-loaded PDF document.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:10:10 +02:00
Marcel
7fb6ec04ab fix(i18n): replace hardcoded German edit hint in CommentMessage with Paraglide key
Adds comment_edit_hint key to de/en/es message files and replaces the
hardcoded "Enter speichern · Esc abbrechen" string in CommentMessage.svelte.

Fixes @felixbrandt + @leonievoss: "hardcoded German bypasses Paraglide"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:14:14 +02:00
Marcel
2b93ccf92d refactor(notifications): import relativeTime from canonical time.ts
NotificationDropdown was importing relativeTime through notifications.ts,
creating an accidental coupling to a module unrelated to timestamp formatting.
Now imports directly from the canonical \$lib/utils/time module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:06:26 +02:00
Marcel
ff9ae198c4 refactor(notifications): extract useNotificationStream and NotificationDropdown from NotificationBell (#200)
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m38s
CI / Backend Unit Tests (push) Failing after 2m50s
CI / Unit & Component Tests (pull_request) Failing after 2m30s
CI / Backend Unit Tests (pull_request) Failing after 2m48s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:54:55 +02:00
Marcel
8898863a48 refactor(transcription): extract useBlockAutoSave and useBlockDragDrop from TranscriptionEditView (#199)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:45:03 +02:00
Marcel
eb8aa92cf0 refactor(pdf): extract usePdfRenderer and PdfControls from PdfViewer (#196)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:34:26 +02:00
Marcel
bc3fec11a9 refactor(comments): extract CommentMessage component from CommentThread (#198)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:23:25 +02:00
Marcel
accfa5373e refactor(unsaved): extract createUnsavedWarning hook and UnsavedWarningBanner
Move the identical isDirty / beforeNavigate / discard pattern out of the
three admin detail pages (groups, tags, users) into a reusable
createUnsavedWarning() hook and a UnsavedWarningBanner presentational
component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:31:17 +02:00
Marcel
8be876492c refactor(date): consolidate formatDate in date.ts with optional format param
Add format?: 'short'|'long' (default 'long') to date.ts formatDate and
remove the duplicate from personFormat.ts. Update DocumentTopBar to
import from date.ts directly. Move the formatDate tests from
personFormat.spec to date.spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:10:44 +02:00
Marcel
76d6f234b4 refactor(personFormat): replace getInitials(Person) with getInitials(name: string)
Unify the initials-extraction logic: the new string-based getInitials()
splits on whitespace, takes the first char of the first and last word
uppercased — matching the pattern that was already inlined in
CommentThread. Update PersonChip, DocumentMetadataDrawer, and
CommentThread to use the shared function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:07:23 +02:00
Marcel
655a2003cb refactor(time): extract relativeTime into shared time.ts utility
Move relativeTime from notifications.ts (Intl.RelativeTimeFormat) to a
new time.ts that uses the Paraglide comment_time_* message keys — the
same logic that was already in CommentThread's timeAgo(). Remove the
duplicate timeAgo() from CommentThread and re-export relativeTime from
notifications.ts for backwards compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:02:49 +02:00
Marcel
c50845bcfc refactor(bell): migrate attachClickOutside to use:clickOutside action (#195)
Replace the inline attachClickOutside attachment in NotificationBell with
the shared use:clickOutside action from $lib/actions/clickOutside. The
inline implementation was functionally identical to the existing action.

Guard the onclickoutside handler so it only calls closeDropdown() when
the notification panel is already open, preventing the bell button from
stealing focus from other interactive elements (e.g. the user avatar menu).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:55:29 +02:00
Marcel
f522ab633c fix(a11y): bump GroupDivider contrast and add separator role
text-xs text-ink/40 (~2.1:1) fails WCAG AA; text-sm bold at text-ink/60
(~3.7:1) passes the large-text 3:1 threshold. Also adds role="separator"
and aria-label so screen readers announce the group boundary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 09:38:37 +02:00
Marcel
69bcb3f8b2 feat(search): add GroupDivider shared component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 23:24:48 +02:00
Marcel
7562a400c0 test(frontend): add Vitest component tests for TrainingHistory expand/collapse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:17:53 +02:00
Marcel
2073a4b64a fix(frontend): accessibility fixes for TrainingHistory expand/collapse and FAILED badge
- Add aria-expanded + aria-controls to expand button (WCAG 4.1.2)
- Add id="training-history-rows" to tbody for aria-controls target
- Replace title= tooltip on FAILED badge with details/summary for keyboard
  and touch accessibility; add training_error_detail_label i18n key
- Use motion-safe:animate-pulse on RUNNING badge for prefers-reduced-motion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:17:53 +02:00
Marcel
f241a71733 feat(frontend): limit training history to 3 runs with expand toggle
Both training panels (OCR and segmentation) share TrainingHistory.
Show only the 3 most recent runs by default; render a Mehr/Weniger
anzeigen button when there are more.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:17:53 +02:00
Marcel
28ac90b529 fix(annotations): replace outline:none with focus-visible ring for keyboard accessibility [M7]
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 1s
CI / Backend Unit Tests (pull_request) Failing after 1s
CI / Unit & Component Tests (push) Failing after 2s
CI / Backend Unit Tests (push) Failing after 1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 14:42:01 +02:00
Marcel
76828a95e3 fix(annotations): add catch(err) binding to handlePointerUp error handler [M6]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 14:41:21 +02:00
Marcel
7125a0a8eb fix(annotations): reset liveWidth/liveHeight in handleKeyDown error rollback [M1, M6]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 14:40:55 +02:00