feat: dashboard enrichment-list-block after batch upload (#296) #298

Merged
marcel merged 19 commits from feat/issue-296-enrichment-list-block into main 2026-04-21 08:59:32 +02:00

19 Commits

Author SHA1 Message Date
Marcel
6f3aa056a1 fix(relativeTime): guard against Invalid Date producing NaN strings
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m36s
CI / OCR Service Tests (pull_request) Successful in 28s
CI / Backend Unit Tests (pull_request) Failing after 2m53s
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
If a row ever receives a malformed uploadedAt (e.g. manual SQL migration,
backend regression), the helper now falls back to "vor 0 Minute(n)"
rather than rendering "vor NaN Tag(en)" to the user.

Addresses Nora's review suggestion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:46:53 +02:00
Marcel
35303831f7 a11y(dashboard): larger dismiss target + motion-reduce + sr-only PDF label
- UploadSuccessBanner dismiss button: 24×24 → 40×40 hit area (icon stays
  at 16px). Matches senior-first baseline Leonie flagged.
- DashboardNeedsMetadata chevron: adds motion-reduce:transition-none and
  motion-reduce:group-hover:translate-x-0 so users with prefers-reduced-
  motion do not see the hover translate.
- Row title prefixed with an sr-only "PDF: " span so assistive tech
  announces the document affordance alongside the title.

Addresses Leonie's review concerns #2, #3, and the sr-only nit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:44:45 +02:00
Marcel
97e8e4fc74 test(dropzone): replace setTimeout flake with vi.waitFor + hoisted mock
The "no-callback" and "no-prop" tests no longer rely on an arbitrary
50ms sleep. Test 2 awaits the mocked invalidateAll call (the last async
step of the upload handler) before asserting the callback was not
invoked. Test 3 lets vitest-browser-svelte's own expect.element poll
until the success message appears.

Addresses Sara's and Felix's review concern about flake-prone timing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:42:23 +02:00
Marcel
30ea1f0dcf test(dashboard): exercise the EnrichmentBlock skeleton branch
Hoists the $navigating store into a shared __mocks__ module so tests can
drive it through real transitions. Adds two specs covering (a) skeleton
visible while $navigating && topDocs empty and (b) skeleton hidden when
topDocs is non-empty. Also sets aria-busy="true" on the skeleton so
screen readers announce the loading state (Leonie's a11y suggestion).

Addresses Sara's and Felix's review concern that the skeleton branch was
dead code in the test world.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:36:41 +02:00
Marcel
d3f9f8457a test(dashboard): extend page.server mock chain for incomplete endpoints
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m39s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m47s
CI / Unit & Component Tests (pull_request) Failing after 2m29s
CI / OCR Service Tests (pull_request) Successful in 27s
CI / Backend Unit Tests (pull_request) Failing after 2m47s
The two "happy path" dashboard load tests now mock the two additional
calls added in f5481289 (/api/documents/incomplete + incomplete-count)
so the Promise.allSettled array resolves fully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:26:31 +02:00
Marcel
3eda482000 test(e2e): dashboard enrichment block — upload + axe sweep
Happy-path journey (upload 2 PDFs → banner → CTA → /enrich) plus axe
sweep at 320/768/1440 × light/dark for the dashboard route. Seeded
docs are cleaned up in afterEach via psql so repeated runs stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:13:37 +02:00
Marcel
f548128940 feat(dashboard): wire EnrichmentBlock between Resume strip and Mission Control
Dashboard loader fetches /api/documents/incomplete?size=5 plus the
existing /incomplete-count and surfaces both via data; +page.svelte
renders EnrichmentBlock with the top 5 docs, the total count, and the
bannerCount state bound to DropZone's onUploadComplete callback
(issue #296).

The block returns null when there is nothing to show, so dashboards
without pending uploads stay uncluttered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:08:09 +02:00
Marcel
90c9ca8708 feat(dropzone): emit onUploadComplete callback with created count
Optional callback lets the parent route pop a post-upload banner without
lifting state into a store. Dashboard uses it to drive
UploadSuccessBanner (issue #296). Only fires when the server actually
created new documents — duplicates and errors do not trigger it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:01:46 +02:00
Marcel
e824e23c8c feat(dashboard): add EnrichmentBlock wrapper component
Composes UploadSuccessBanner + DashboardNeedsMetadata and reserves a
360px skeleton while \$navigating re-runs the loader with a fresh
incomplete list. Prevents the layout-shift jump after a batch upload
(Leonie's resolved decision #3 on issue #296).

Renders nothing when there is nothing to show — keeps the clean empty
dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:58:05 +02:00
Marcel
b29125615f feat(dashboard): add UploadSuccessBanner component
Transient post-upload banner for issue #296: singular/plural German copy,
aria-live=polite for screen readers, manual X dismiss, 8s auto-dismiss.
"Jetzt ergänzen →" CTA links directly to /enrich so seniors can continue
straight into the enrichment flow after a batch upload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:53:28 +02:00
Marcel
01e72611f0 feat(dashboard): redesign needs-metadata with row anatomy + totalCount
Switches to two props — topDocs (max 5, capped by caller) and totalCount —
so the footer link can surface "Alle 12 anzeigen →" even when only 5
items are shown. Each row gets a generic document icon, title, relative
upload time and a chevron, wrapped in a single <a> per the issue spec.

Still returns null when topDocs is empty, keeping the empty dashboard
clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:34:23 +02:00
Marcel
727569aa32 i18n: add upload-banner + enrichment-block keys
Singular/plural banner copy, a count-aware "show all" footer link, and
the dismiss aria-label for the new dashboard enrichment-list-block
(issue #296). Covers de / en / es.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:25:17 +02:00
Marcel
d5d1a463b8 feat(frontend): add relativeTimeDe helper for dashboard meta lines
Pure function with injectable now — lets the dashboard enrichment block
render "vor 2 Min." / "vor 3 Std." / "vor 2 Tagen" without clock-based
test flakiness. Reuses the existing comment_time_minutes / _hours /
_days Paraglide keys, no new translations needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:22:59 +02:00
Marcel
46fe3655ab chore(frontend): regenerate openapi types for /api/documents/incomplete
Picks up the restored list endpoint and the new uploadedAt field on
IncompleteDocumentDTO (issue #296).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:19:46 +02:00
Marcel
47859e5a9b feat(documents): retrofit WRITE_ALL guard on /incomplete-count + /incomplete/next
Closes the CWE-285 gap Nora flagged on issue #296: both endpoints expose
enrichment-queue information that only writers should see. Brings them in
line with the new /incomplete list endpoint and every other write-path
under DocumentController.

Frontend callers (/enrich/[id]/+page.server.ts) already gate on WRITE_ALL
at the route level, so no client-side change is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:14:24 +02:00
Marcel
758c708766 test(documents): lock /incomplete size cap at 200
Regression test proving the controller clamps client-supplied size
values server-side, closing the unbounded-limit concern Markus flagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:09:10 +02:00
Marcel
2c5cfcedbc feat(documents): gate /incomplete behind WRITE_ALL permission
Only users who can enrich documents should see the queue.
Mirrors the frontend guard in enrich/+page.server.ts and closes the
CWE-285 gap Nora flagged on issue #296.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:05:35 +02:00
Marcel
bc3a268f66 feat(documents): re-add GET /api/documents/incomplete
Restores the list endpoint removed in ddd811c6 and caps size at 200.
The dashboard enrichment block (issue #296) and /enrich page both
consume it; /enrich was silently 404ing since the deletion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:01:54 +02:00
Marcel
d2fc452c1a feat(dto): add uploadedAt to IncompleteDocumentDTO
Mapper populates uploadedAt from Document.createdAt so the dashboard
enrichment block can show a relative-time meta line ("vor 2 Min.")
per issue #296.

LocalDateTime matches the convention used by NotificationDTO,
DocumentVersionSummary and InviteListItemDTO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:58:15 +02:00