feat/issue-283-sender-receiver-grouping #284
Reference in New Issue
Block a user
Delete Branch "feat/issue-283-sender-receiver-grouping"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ⚠️ Approved with concerns
Blockers
sortprop typed asstringinstead of a union literal type —DocumentList.svelte, props declaration:A typo like
sort="SNEDER"silently falls through to year grouping — no compiler error, no runtime error, just wrong behaviour. Fix:'Ohne Datum'is a hardcoded German string ingroupByYear()whilegroupBySenderandgroupByReceiverboth usem.docs_group_unknown_sender()/m.docs_group_unknown_receiver(). The inconsistency is inDocumentList.svelte— either add anm.docs_group_no_date()key or at least add the German, English, and Spanish keys to be consistent with the pattern used directly below.Suggestions
year-carddata-testid is semantically wrong now — the sender and receiver grouping tests also checkpage.getByTestId('year-card')to count cards. Renaming it togroup-cardin the template and tests would make the intention clear for future readers.groupBy*functions share a nearly identicalSvelteMappattern — I considered suggesting extraction, but the receiver case is genuinely different (multi-bucket fan-out per item), so KISS wins. Good call keeping them separate.makeItemfactory function pattern is used consistently throughout. Thesender: null → undefinedfix ensures type alignment. Well done.🏛️ Markus Keller — Application Architect
Verdict: ✅ Approved
This is a clean, purely presentational change. All grouping logic runs client-side on already-loaded data — no new API surface, no backend coupling, no database queries added. The architecture is solid:
DocumentListremains a pure component: it takesitems: DocumentSearchItem[]andsortas props and derives its view. No side effects, no external calls.sortdispatch happens in the parent page (+page.svelte), which is the right orchestration layer.SvelteMapused in the group functions is correct — plainMapmutations are invisible to Svelte's reactivity system.group.label + '-' + item.document.idin the{#each}block is a smart move: it handles the receiver fan-out case where the same document ID appears under multiple groups.Minor note: The
sortprop is typed asstringin the props interface. For an architectural view this is a weak contract — Felix flagged it too. A union literal type ('DATE' | 'SENDER' | 'RECEIVER') would make the component's contract explicit and eliminate the silent fallback risk. No structural consequence, but worth tightening.No layer boundary violations, no accidental complexity, no premature abstractions. Good scope control on this feature.
🔒 Nora "NullX" Steiner — Application Security Engineer
Verdict: ✅ Approved
No security concerns in this PR. A clean LGTM from me — here's what I checked:
DocumentSearchItem[]data. Thesortprop is a UI state variable, not a database query parameter.displayNamerendered in group headers comes from the backend — it's pre-stored data that passed through the existing persistence layer, not a raw user-controlled string injected into the current request. No XSS vector here.sender: null → undefinedchange is a TypeScript alignment fix with no security implication.The one thing I'd note for future reference: if the
sortURL parameter ever gets proxied into a backend query (e.g., a future server-side sorting feature), validate and whitelist it strictly at the server boundary before it touches any query logic. Right now it's safe because the sort is purely frontend-computed.🧪 Sara Holt — QA Engineer & Test Strategist
Verdict: ⚠️ Approved with concerns
The new test coverage is solid overall — three well-structured
describeblocks, consistent use of themakeItemfactory, and tests for both the happy path and fallback labels. Thesender: null → undefinedfix aligns test data with the actual TypeScript types.Concerns
year-carddata-testid is semantically misleading in sender/receiver tests — the sender and receiver grouping tests both usepage.getByTestId('year-card')to count group cards:This works today but will confuse future test readers: "why is a sender grouping test looking for
year-card?" Rename the testid in the template togroup-cardand update all three test suites for consistency.Missing edge case: unknown
sortvalue — the$derived.by()dispatch silently falls through to year grouping for any unrecognisedsortvalue. This is reasonable behaviour, but it's untested. If a stale bookmark or URL parameter sendssort=SUBJECT, the user silently sees year groups with no indication of the fallback. A single test case would document the intent:What's Good
'Unbekannter Absender','Unbekannter Empfänger'), which will catch i18n regressions.🎨 Leonie Voss — UX Designer & Accessibility Strategist
Verdict: ⚠️ Approved with concerns
Concern —
uppercasetransform on person names in group headersThe group header uses:
For year grouping,
uppercaseon "2024" is fine — it renders the same. But for sender/receiver grouping, "Max Mustermann" becomes "MAX MUSTERMANN", which:Fix: Conditionally suppress the transform for name-based group modes, or apply CSS
font-variant: small-capswhich preserves case while maintaining the visual weight:What's Good
items-start+h-9 items-centerwrapper inDocumentRow.sveltecorrectly fixes the vertical alignment — the ProgressRing was stretching to match the ContributorStack height before. Clean fix.data-testid="group-header"added to the span — good for both testing and accessibility tooling.Minor
The
data-testid="year-card"on the group container is now a misnomer. Renaming togroup-cardwould improve future readability (Sara flagged this too from a testing angle).🛠️ Tobias Wendt — DevOps & Platform Engineer
Verdict: ✅ Approved
Nothing here touches infrastructure. This is a pure frontend feature — Svelte component logic, i18n strings, and Vitest tests. No changes to
docker-compose.yml, CI workflows, environment variables, image tags, or deployment configuration.LGTM from a platform perspective. No action needed on my end.
✅ Review concerns addressed
All review concerns from this round have been resolved. Here's what was done:
5c2378a6—data-testid="year-card"→group-cardRenamed in both
DocumentList.svelteand all four spec assertions. Addresses: @felixbrandt, @saraholt, @leonievoss.a62ccd42— Hardcoded'Ohne Datum'→m.docs_group_undated()groupByYear()now uses the pre-existingdocs_group_undatedi18n key ("Undatiert" / "Undated" / "Sin fecha"). The corresponding spec updated from'Ohne Datum'to'Undatiert'. Addresses: @felixbrandt.4cdec7ec—sortprop type narrowed fromstringtoSortModeSortMode = 'DATE' | 'TITLE' | 'SENDER' | 'RECEIVER' | 'UPLOAD_DATE' | 'RELEVANCE'— matches the full API enum so+page.sveltepasses without a cast. Addresses: @felixbrandt, @mkeller.506a220a— Regression test for sort fallback behaviourAdded a test asserting that
sort='TITLE'(and any non-SENDER/non-RECEIVER value) falls back to year grouping. Addresses: @saraholt.1ac29188— Suppressuppercaseon person name group headersuppercaseandtracking-widestare now conditional onsort !== 'SENDER' && sort !== 'RECEIVER'; person name headers gettracking-wideinstead. Addresses: @leonievoss.17 tests passing, no new type check errors, lint clean.
👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ✅ Approved
Every concern from round 1 has been resolved cleanly.
What I checked:
SortMode = 'DATE' | 'TITLE' | 'SENDER' | 'RECEIVER' | 'UPLOAD_DATE' | 'RELEVANCE'— the right call. Matches the full backend enum so+page.svelteneeds no cast. Typos are now caught at compile time.m.docs_group_undated()replaces'Ohne Datum'— all three groupBy functions now use i18n consistently.group-cardtestid is consistent everywhere — template and all four spec assertions.sort='TITLE'→ year grouping verified as a regression guard. Passes immediately (existing behaviour) — appropriate for a guard test.uppercase/tracking-widestvstracking-widefor person name headers — correct and clean.TDD discipline: 17 tests covering year grouping, sort fallback, sender grouping (3 cases), receiver grouping (3 cases), and DocumentRow delegation. Each describe block tests one behaviour axis. Factory function used consistently throughout. Red/green sequence was clean.
Code quality: Three separate
groupBy*functions — the receiver fan-out case genuinely differs from the sender/year patterns, so keeping them separate is the right KISS call. No abstraction for its own sake.Ready to merge.
🏛️ Markus Keller — Application Architect
Verdict: ✅ Approved
All architectural concerns from round 1 are resolved.
SortModenow correctly spans the full backend enum — the component's prop interface is honest about what it accepts, and the implementation handles the three grouping strategies while gracefully falling through to year-grouping for anything else. That's the right layering: the component knows about grouping modes, not about what the backend sorts by.The composite
{#each}keygroup.label + '-' + item.document.idcorrectly handles the receiver fan-out case where the same document ID appears in multiple groups. Subtle, but correct.Architecture remains clean:
DocumentListis a pure presentational component. No new backend coupling, no side effects. Thesortprop flows down from+page.sveltewhich is the right orchestration layer.This is ready to merge.
🔒 Nora "NullX" Steiner — Application Security Engineer
Verdict: ✅ Approved
Nothing changed from a security perspective. Still a pure client-side rendering feature — no new API surface, no user input reaching any query, no new server-side data flows. The
displayNamevalues in group headers come from pre-stored backend data, not from the current request.The i18n key additions are static translations with no interpolated user content.
Clean bill of health. Ready to merge.
🧪 Sara Holt — QA Engineer & Test Strategist
Verdict: ✅ Approved
All round-1 concerns addressed.
group-cardtestid is now consistent across all four test call sites — the semantic confusion is gone.sort='TITLE'→ shows'2024'group header) documents the existing behaviour as a regression guard. Correct approach for an already-implemented fallback.makeItem()used consistently — no ad-hoc construction in test bodies.The test suite now covers every code path in
DocumentList.svelte— year, SENDER, RECEIVER, and the fallback case. This is what a complete unit test layer looks like.Ready to merge.
🎨 Leonie Voss — UX Designer & Accessibility Strategist
Verdict: ✅ Approved
Both concerns from round 1 are resolved.
The conditional CSS is clean:
Person names like "Max Mustermann" now render in normal case with
tracking-wide— readable, respectful, appropriately weighted. Year labels like "2024" keep theuppercase+tracking-wideststyle that gives them the archival heading feel. The distinction is deliberate and correct.The
group-card/group-headertestid rename is a nice side effect for anyone doing visual regression testing later.The
items-startalignment fix inDocumentRow.svelteis solid — prevents the ProgressRing from stretching when the ContributorStack is taller than expected.Everything I would have flagged is resolved. Ready to merge.
🛠️ Tobias Wendt — DevOps & Platform Engineer
Verdict: ✅ Approved
Still no infrastructure changes. Pure frontend feature — Svelte components, i18n strings, and Vitest tests. Nothing in
docker-compose.yml, CI workflows, environment variables, or deployment config.LGTM. Ready to merge.