feat(chat): usable 1:1 chat — bottom-anchored, Drift-backed, dated

- Anchor the message list to the bottom (`reverse: true`) so new messages
  stay in view instead of landing below the fold.
- Move chat history from the OS keystore (O(n²) JSON blob, silent 200-msg
  cap) to a separate encrypted Drift/SQLCipher DB (`ChatDatabase`):
  indexed append, uncapped history, dedup as a unique-key invariant. It's
  an ephemeral per-device cache, isolated from the inventory schema, its
  migrations, and its sync. No data migration (pre-release).
- Add day separators (Today/Yesterday/locale date) and a per-bubble time,
  all via ICU (12/24h per locale; Localizations locale maps Asturian →
  Spanish for intl date symbols).
- Add peer avatars (deterministic colour from the pubkey + name initial),
  surface send failures that were previously silent, and make bubble text
  selectable (addresses, links).
- New i18n keys in en/es/pt/ast; tests for grouping, formatting, avatars,
  scroll anchoring, storage and send errors.

Docs: docs/design/chat-storage.md + open-decisions.md.
This commit is contained in:
vjrj 2026-07-11 06:39:39 +02:00
parent 171daabce3
commit 6cff0d0b11
27 changed files with 1793 additions and 264 deletions

View file

@ -1437,6 +1437,15 @@ class Translations$chat$en {
/// en: 'Ğ1 address copied — paste it in your wallet'
String get g1Copied => 'Ğ1 address copied — paste it in your wallet';
/// en: 'Today'
String get today => 'Today';
/// en: 'Yesterday'
String get yesterday => 'Yesterday';
/// en: 'Couldn't send check your connection'
String get sendError => 'Couldn\'t send — check your connection';
}
// Path: trust
@ -2536,6 +2545,9 @@ extension on Translations {
'chat.offline' => 'Set up sharing to send messages',
'chat.payG1' => 'Pay in Ğ1',
'chat.g1Copied' => 'Ğ1 address copied — paste it in your wallet',
'chat.today' => 'Today',
'chat.yesterday' => 'Yesterday',
'chat.sendError' => 'Couldn\'t send — check your connection',
'trust.none' => 'No one vouches for them yet',
'trust.count' => ({required Object n}) => 'Vouched for by ${n}',
'trust.vouch' => 'I know this person',