feat: reply + search + forward + archive/mute + error boundary

Messages:
- Reply-to: hover action, composer chip with cancel, quote bubble inside
  the replying message with tap-to-jump + amber highlight ring
- Search: header search button toggles in-conversation search bar with
  prev/next + match counter, auto-jump to active match
- Forward: multi-select conversation picker. Attachments are now carried
  over: download + decrypt source, re-encrypt under each target conv-key,
  re-upload with fresh per-attachment keys, insert new attachment rows

Conversations:
- Archive + mute per member. New migration 20260420000001 adds `archived`
  + `muted_until` on conversation_members. Shared helpers:
  setConversationArchived / setConversationMutedUntil / isConversationMuted
- ChatsPage: archive toggle in header with unread badge for archived
  bucket, split active/archived lists, muted indicator (BellOff icon,
  dimmed unread badge)
- ConversationRowMenu via createPortal (escapes sidebar overflow clip),
  forwardRef-based MenuItem so submenu positioning refs survive React 18
- ConversationsContext: suppresses notification sound + OS notif when
  target conversation is muted
- Refresh on `profiles UPDATE` realtime so peer avatar / displayName
  changes flow to conversation.members without manual refresh

Resilience:
- ErrorBoundary (Discord-style): centred spinner + escalating copy, no
  manual reload button. Backoff retry schedule [2s, 4s, 8s, 15s, 30s].
  Uses a keyed Fragment (not a div wrapper) so flex h-full chains survive
- App wrapped root + per-route RouteBoundary, conversation-level boundary
- AuthContext: flip `ready` immediately on cached session read; validate
  getUser in background so a stalled/offline Supabase doesn't freeze the
  app on the loading spinner

Crypto:
- Swap libsodium-wrappers -> libsodium-wrappers-sumo (compact build was
  missing crypto_pwhash so Argon2id vault KDF threw, falling back to
  plaintext localStorage on every launch)
- Shim d.ts for sumo types (sumo is API superset, no official types ship)
- vite optimizeDeps includes sumo with the "require" condition
- secureFileStore: exists(dir) check before mkdir; surface genuine
  permission errors instead of silent catch

Tauri:
- fs scope adds `$APPLOCALDATA` direct entry (not just /**) so the app
  data directory itself can be mkdir'd on first launch

Chat layout:
- Skip call_event messages when computing avatar run boundaries so a
  regular bubble followed by a call event from the same sender still
  shows its avatar
This commit is contained in:
2026-04-20 15:42:49 +02:00
parent 1fab2edc57
commit de431386ea
28 changed files with 1574 additions and 150 deletions
+31 -1
View File
@@ -33,7 +33,37 @@
"call_incoming": "Eingehender Anruf",
"call_missed": "Verpasster Anruf",
"call_no_answer": "Keine Antwort",
"call_declined": "Anruf abgelehnt"
"call_declined": "Anruf abgelehnt",
"you": "Du",
"attachment": "Anhang",
"reply": "Antworten",
"forward": "Weiterleiten",
"replying_to": "Antwort an {{name}}",
"quote_unavailable": "Nachricht nicht verfügbar",
"search_in_conv": "In Unterhaltung suchen…",
"search_none": "Keine Treffer",
"forward_preview": "Vorschau",
"forward_attachments_dropped": "Anhänge werden nicht mit weitergeleitet.",
"forward_no_targets": "Keine anderen Unterhaltungen verfügbar.",
"forward_done": "Gesendet",
"forward_send": "An {{count}} senden",
"forward_attachments_count_one": "{{count}} Anhang wird mit weitergeleitet",
"forward_attachments_count_other": "{{count}} Anhänge werden mit weitergeleitet",
"archive": "Archivieren",
"unarchive": "Entarchivieren",
"archived_title": "Archiv",
"show_archived": "Archiv anzeigen",
"show_active": "Aktive anzeigen",
"archived_empty_title": "Nichts archiviert",
"archived_empty_subtitle": "Archivierte Unterhaltungen erscheinen hier.",
"mute": "Stummschalten",
"unmute": "Stummschaltung aufheben",
"mute_1h": "1 Stunde",
"mute_8h": "8 Stunden",
"mute_24h": "24 Stunden",
"mute_1w": "1 Woche",
"mute_forever": "Bis auf Weiteres",
"row_menu": "Aktionen"
},
"call": {
"start_audio": "Sprachanruf",
+31 -1
View File
@@ -33,7 +33,37 @@
"call_incoming": "Incoming call",
"call_missed": "Missed call",
"call_no_answer": "No answer",
"call_declined": "Call declined"
"call_declined": "Call declined",
"you": "You",
"attachment": "Attachment",
"reply": "Reply",
"forward": "Forward",
"replying_to": "Replying to {{name}}",
"quote_unavailable": "Message not available",
"search_in_conv": "Search in conversation…",
"search_none": "No matches",
"forward_preview": "Preview",
"forward_attachments_dropped": "Attachments are not forwarded.",
"forward_no_targets": "No other conversations available.",
"forward_done": "Sent",
"forward_send": "Send to {{count}}",
"forward_attachments_count_one": "{{count}} attachment forwarded",
"forward_attachments_count_other": "{{count}} attachments forwarded",
"archive": "Archive",
"unarchive": "Unarchive",
"archived_title": "Archive",
"show_archived": "Show archive",
"show_active": "Show active",
"archived_empty_title": "Nothing archived",
"archived_empty_subtitle": "Archived conversations appear here.",
"mute": "Mute",
"unmute": "Unmute",
"mute_1h": "1 hour",
"mute_8h": "8 hours",
"mute_24h": "24 hours",
"mute_1w": "1 week",
"mute_forever": "Until further notice",
"row_menu": "Actions"
},
"call": {
"start_audio": "Voice call",