Commit graph

9 commits

Author SHA1 Message Date
King Omar
5d41ed72f4 Add SEO: sitemap.xml, robots.txt, meta tags + structured data
- sitemap.xml (homepage) + robots.txt (allow crawl, disallow /api/, link sitemap)
- <head>: SEO title, meta description, keywords, canonical, robots
- Open Graph + Twitter Card tags for link previews
- JSON-LD WebApplication structured data (features, free, publisher)
- <noscript> crawlable content so the JS-rendered SPA has indexable text
- SW cache v11

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:50:35 +10:00
King Omar
726d9d53ba Add read receipts, disappearing messages, and group invite links
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>
2026-07-22 18:32:24 +10:00
maverick
3d9f712867 Rebrand to Radical Party identity
Match theradicalparty.com brand pulled from sibling apps:
- signature red #FF2D55 (+ hot pink #FF6B82) on warm near-black #0B0B09, cream #F0EDE6
- Syne (display/brand/headings) + IBM Plex Mono (recovery/linking codes) via Google Fonts
- warm red/pink/gold aurora backdrop; red accent glows, focus rings, bubbles throughout
- auth wordmark in Syne + 'A Radical Party Project' tag + 'Vibe, Vote, Veto' tagline
- red lock icon, updated manifest name/description/theme, theme-color #0B0B09
- SW cache v6->v7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:08:59 +10:00
maverick
67367d5e83 Add WebRTC E2E voice/video calling (1:1)
- call.js: CallManager — RTCPeerConnection lifecycle, offer/answer/ICE/bye state
  machine, mute/camera toggles, hangup. STUN (Google + Cloudflare) for NAT.
- Signaling (SDP + ICE) rides the existing E2E message relay as {k:"call"}
  payloads — never seen by the server in clear. Media is P2P, encrypted by
  WebRTC's mandatory DTLS-SRTP, so audio/video is end-to-end and never hits R2.
- app.js: call buttons (📞/🎥) in DM headers, incoming/outgoing/connected call
  overlay with video tiles + PiP, mute/camera/hangup, ring pulse, duration timer.
- Verified signaling state machine (mocked WebRTC): both sides reach 'connected'.
  Group calls not supported yet (needs SFU). SW cache v5->v6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 13:21:52 +10:00
maverick
c48f9f4006 Encrypted group chats + major UI redesign
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>
2026-07-22 13:16:16 +10:00
maverick
34c7ef08eb Add multi-device key sync: QR device linking + encrypted recovery code
Answers 'how do you get keys to login elsewhere' — both mechanisms, Signal/WhatsApp style:

Server:
- migration 0002: key_backups (recovery blobs) + provisions (QR relay), ciphertext only
- routes/backup.js: POST/GET /api/backup, /backup/exists; provision new/get/complete
- routes/keys.js: POST /api/keys/reset — atomically wipe stale prekeys + install fresh
  (fixes Bad MAC: restore regenerates prekey IDs that INSERT-OR-IGNORE kept stale)

Crypto (crypto.js):
- exportIdentityBlob/importIdentityBlob (identity travels only encrypted)
- generateRecoveryCode (120-bit Crockford base32) + encrypt/decryptWithCode (PBKDF2 250k)
- ephemeral ECDH (P-256) encrypt/decrypt for QR transfer

Client (app.js): account-menu 'Set up recovery code' + 'Link a device' (camera scan
via BarcodeDetector or paste); auth-screen 'Restore with recovery code' + 'Link from
another device' (shows QR + code, polls, restores). Vendored qrcode-generator.

All verified live end-to-end (3-process isolated-store integration tests):
restore-from-code and QR-link both let a fresh device receive + decrypt messages.
SW cache v3->v4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 13:04:46 +10:00
maverick
bd112d07fe Fix: namespace encrypted store per-account so two accounts can coexist on one browser
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>
2026-07-22 02:48:00 +10:00
maverick
fc3f79379e Fix New chat button: replace window.prompt with in-app modal
- window.prompt/alert are suppressed in Brave and installed PWAs, so the
  button appeared to do nothing. Replace with a proper modal (inline error,
  Enter/Escape, click-outside to close).
- Mobile nav: sidebar was fully hidden (button unreachable); now sidebar shows
  by default and a back button toggles between list and thread.
- Bump SW cache v1->v2 so the new shell actually ships past the cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 02:20:14 +10:00
maverick
360bfeb5ab Cipher: E2E encrypted messenger scaffold (Signal Protocol + Cloudflare)
- Hono Worker: auth, key directory (X3DH prekey bundles), message relay, R2 media
- Mailbox Durable Object: WebSocket delivery + offline ciphertext queue
- PWA client: libsignal (vendored), IndexedDB key store, chat UI, media E2E
- Server only ever holds public keys + ciphertext; private keys stay on device

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 01:56:59 +10:00