Developer specification for the WhatsApp/SMS-inspired chat layout. Constrains all bubble content to a centred 640 px column so opposing bubbles are close together — familiar to older users, easier to follow on wide screens.
| File | What changes | Approx. lines touched |
|---|---|---|
frontend/src/routes/conversations/ConversationFilterBar.svelte |
Add expanded bindable prop. Render either a collapsed strip or the full form depending on state. Add "Adjust" button. Add "Apply"/"Cancel" controls for overlay mode. |
+38 / −2 |
frontend/src/routes/conversations/ConversationTimeline.svelte |
Remove central vertical line. Wrap bubble list in max-w-[640px] mx-auto column. Change bubble max-width. Add status text labels alongside status dots. |
+14 / −6 |
frontend/src/routes/conversations/+page.svelte |
Add filterExpanded reactive state. Auto-collapse when both persons are selected and documents load. Pass bind:expanded to FilterBar. |
+8 / −1 |
frontend/src/lib/paraglide/messages/*.json (de / en / es) |
Add 6 new i18n keys: conv_filter_adjust + 5 status label keys. |
+6 per file |
/conversations — query params (senderId, receiverId, from, to, dir) are the only data contract. No changes to +page.server.ts.filterExpanded flag lives in +page.svelte (not FilterBar) so the parent controls auto-collapse behaviour after navigation.transition: max-height or a Svelte #if block — developer's choice, but the spec uses #if for simplicity.max-w-[640px] — no new CSS variables needed.Unchanged: +page.server.ts, the API client, all repository/service code, URL query-param names, Paraglide message IDs already in use, canWrite guard, and all existing test IDs (data-testid).
expanded = $bindable(true) to FilterBar's prop definition. Default true so existing tests that don't pass the prop still see the full form.+page.svelte: let filterExpanded = $state(!data.filters.senderId || !data.filters.receiverId). After successful load with results, set filterExpanded = false inside the $effect that syncs filter values.onapplyFilters() then sets expanded = false (local) — the parent's bound state updates via Svelte 5 bindable.expanded = false without calling onapplyFilters().| Container | max-w-[80%] (was md:max-w-[70%]). No breakpoint prefix needed — column is already narrow. |
| Corner cut | rounded rounded-br-none — WhatsApp-style tail at bottom-right |
| Background | bg-primary (#002850 navy) |
| Title | font-serif text-sm font-medium text-primary-fg |
| Meta row | font-sans text-[10px] tracking-wider uppercase text-primary-fg/70 |
| Status — NEW | flex items-center gap-1 → dot + text label (see note below) |
| Hover | hover:-translate-y-0.5 hover:shadow-md — retained unchanged |
| Avatar | hidden sm:flex — 32×32, navy fill, white initials |
| Corner cut | rounded rounded-bl-none — tail at bottom-left |
| Background | bg-muted/50 (light grey, ~#E8E4DF) |
| Title | font-serif text-sm font-medium text-ink |
| Meta row | font-sans text-[10px] tracking-wider uppercase text-ink-2 |
| Status — NEW | Same structure, label colour text-ink-2 (dark enough for contrast) |
| Avatar | hidden sm:flex — 32×32, white bg, ink initials, border |
| DocumentStatus | Dot colour class | Label key (i18n) | de | Sender text colour | Receiver text colour |
|---|---|---|---|---|---|
PLACEHOLDER | bg-yellow-400 | conv_status_placeholder | Platzhalter | text-primary-fg/60 | text-ink-2 |
UPLOADED | bg-accent (green) | conv_status_uploaded | Hochgeladen | text-primary-fg/65 | text-ink-2 |
TRANSCRIBED | bg-blue-400 | conv_status_transcribed | Transkribiert | text-primary-fg/65 | text-ink-2 |
REVIEWED | bg-purple-400 | conv_status_reviewed | Geprüft | text-primary-fg/65 | text-ink-2 |
ARCHIVED | bg-gray-400 | conv_status_archived | Archiviert | text-primary-fg/50 | text-ink-3 |
Implement as a const statusLabels: Record<string, string> in ConversationTimeline.svelte using m.conv_status_* functions. Fall back to doc.status for unknown values.
<div class="relative flex items-center py-2 text-center"> <div class="flex-grow border-t border-line"></div> <span class="mx-4 font-sans text-xs font-bold tracking-widest text-ink/40 uppercase">{year}</span> <div class="flex-grow border-t border-line"></div></div>max-w-[640px] mx-auto wrapper div instead of the previous full-width flex column.
The mb-4 flex items-center justify-between summary div in ConversationTimeline.svelte is unchanged — it is already outside the chat container div. No code change needed for the summary bar itself.
md:max-w-[70%] — up to 70 % of full page at desktoptitle tooltip (WCAG failure)bg-accent (uploaded) vs bg-yellow-400 (all else)hidden sm:block — correct but max-width too widemax-w-[640px] column — tight WhatsApp-style gapmax-w-[80%] — 80 % of 640 px = ~512 px, same visual weight across breakpointsDocumentStatus lifecyclemax-w-[85%]