Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
jack
c58ad9af10 Review fixes: unread flag + favorites-aware naming for the warning
- Codex P1: a background identity change appended the warning without
  marking the chat unread — a security event nobody was looking at
  stayed invisible until the conversation was manually opened. It now
  sets the unread flag unless the chat is open.
- Codex P2: offline key rotation is the common case here, and
  resolveNickname falls back to an anon prefix precisely then (no mesh
  nickname, no social identity for the unverified new fingerprint).
  The persisted favorite relationship's nickname is preferred.

Both pinned by tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 10:09:57 +02:00
jack
0a288869d6 Merge branch 'feat/ptt-consent' into feat/identity-changed-warning 2026-08-11 10:06:48 +02:00
jack
6dd14961c6 Review fix: opt live-voice tests in via injectable provider
Codex P1: the fixtures assumed the old default-ON preference — on a
clean CI process the coordinator guard drops their frames. Instead of
mutating the shared UserDefaults per test (which races parallel
suites), the live-voice reads are now injectable
(ChatLiveVoiceCoordinator.liveVoiceEnabled), the fixtures opt in per
instance, and the toggle-off test drives the provider directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 10:06:45 +02:00
jack
daf9738c8b Merge branch 'feat/read-receipt-toggle' into feat/ptt-consent 2026-08-11 10:03:05 +02:00
jack
cfa875459d Review fix: record withheld receipts in both tracking sets
Codex P1: the manager-path withheld claim landed only in
PrivateChatManager.sentReadReceipts, while the lifecycle read pass
dedups against ChatViewModel's persisted set — enabling receipts
before the next lifecycle pass could send a receipt for a message
read while the setting was off. The withheld branch now records into
the owner's persisted set too (markReceiptHandled, wired in the
bootstrapper); test strengthened to require both sets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 10:03:01 +02:00
jack
359139fc6b Warn in the chat when a peer's identity key changes
migrateNoiseKeyUpdate silently merged the DM thread onto the new key:
a peer who panic-wiped (or was replaced by whoever holds the nickname)
inherited the conversation's earned trust with no visible sign beyond
a debug log, and any earlier verification — bound to the OLD
fingerprint — vanished without a word. Signal treats this as a
full-width banner; bitchat treated it as bookkeeping.

The migration now appends a system line to the affected conversation:
"<name>'s identity key changed — this can mean a new device or a
reset. earlier verification no longer applies; verify them again
before trusting this chat." Only when there is a conversation to
protect (selected chat or migrated messages) — a favorite never
chatted with doesn't spawn a warning-only thread.

This is also the prerequisite UX for the peer-ID rotation project:
once rotation ships, identity changes become routine and MUST be
visible. 1 string x 30 locales; behavior pinned both ways by new
coordinator tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:28:11 +02:00
jack
0dacb3629c PTT consent: live voice off by default; slide-away-to-cancel
Live push-to-talk defaulted ON in both directions: holding the mic in
a public channel streamed your voice to everyone in radio range before
you released, and inbound bursts auto-played out of the speaker. A
single buried toggle defaulting on was not consent — both behaviors
are now opt-in (PTTSettings.liveVoiceEnabled defaults false), and the
settings copy says exactly what turning it on does.

Recording could also not be aborted one-handed: release always sent,
and the HUD's cancel button required lifting the finger — which sent.
Sliding the finger off the mic button (>60pt) now arms cancel — the
HUD flips from the timer to "release to cancel" — and sliding back
re-arms send. The armed flag resets on every exit path (cancel,
finish, panic), pinned by a new test.

2 new strings + 1 updated, all 30 locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:23:19 +02:00
jack
3e46829f32 Add a read-receipt toggle
Read receipts were unconditional: on chat open and on didBecomeActive,
twice. A receipt is a presence oracle — it says the person is awake,
holding their phone, and opened the app at that exact moment, which is
precisely the metadata the stated threat model says someone should be
able to withhold. Every mainstream messenger ships this switch.

- ReadReceiptSettings (default ON = existing behavior), toggle in the
  settings privacy section, reset on panic wipe.
- All four origination paths gated: mesh/nostr routing, direct mesh
  receipts, geohash receipts, and PrivateChatManager's read pass.
  Withheld receipts are still recorded locally as sent, so re-enabling
  the setting never fires a retroactive burst disclosing past reads.
- Only outbound receipts are affected; receipts from others display.
- 2 strings x 30 locales; tests pin the default, the reset, and that
  nothing reaches the transport while off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:17:55 +02:00
17 changed files with 1349 additions and 42 deletions