Receipts: messages now carry a shared client id (mid); recipients send
delivered/read receipts over the E2E relay, rendered as ✓/✓✓ ticks (blue
when read). Groups aggregate — ticks turn on once every member acks.
Disappearing messages: per-chat timer (Off..1 week) stored locally and
synced to peers via an encrypted config control message; messages carry a
ttl and a client-side sweeper deletes them from both sides on schedule.
Group invite links: new group_invites table + /groups/:id/invite (mint/
revoke) and /groups/join routes. Group info gains "Add people" and
"Invite via link" (shareable URL + QR); opening a #join=<token> link joins
the group and re-syncs every member's roster over the relay.
store.js v3 adds a mid index + getByMid/put/sweepExpired; SW cache v9.
Verified: 12 live server tests (invite/join/add/revoke/leave) + 11
store unit tests (receipts, sweep, meta) all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groups (E2E via pairwise fan-out — sender encrypts each message separately to
every member with existing Signal sessions; server holds only name+membership):
- migration 0003: groups + group_members; routes/groups.js (create/list/get/add/leave)
- client: unified DM+group chat model, group create modal w/ member chips,
fan-out deliver, groupId payload routing on receive, lazy group fetch,
sender names in group bubbles, group info + leave
UI overhaul — glassmorphism redesign:
- animated aurora backdrop, blurred glass panels, violet→cyan gradient brand
- color-hashed gradient avatars (per name), group avatars
- message bubbles with rise animation + tails, gradient outgoing bubbles
- polished auth card w/ gradient border, pill actions, modal pop-in, toast styling
- responsive: full-width thread on mobile with back button
Verified live: 3-user group message fans out and both members decrypt.
SW cache v4->v5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of 'two users can't chat': all accounts on one browser shared a
single IndexedDB, so the second registration overwrote the first's Signal
identity keys, corrupting both. Verified via two-isolated-client integration
test (real crypto.js + store.js) that the protocol itself is correct.
- store.js: useAccount(username) namespaces IndexedDB as cipher:<username>;
keys generated at registration now persist under the right account
- accounts.js: localStorage-backed account registry + active pointer
- app.js: multi-account boot/activate, account-switch/add/logout menu,
error toasts on send/decrypt failures (no more silent failures)
- login guards against missing on-device keys (multi-device linking still TODO)
- SW cache v2->v3 + precache accounts.js
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>