* Replace try! regex construction with a non-trapping SafeRegex helper
MessageFormattingEngine and MessageDeduplicationService compiled eight
bundled regex literals with try!, so a bad pattern would crash the app
at startup - in the middle of the message-render path (#645).
Add SafeRegex.compile: it compiles the pattern normally, and on failure
logs through SecureLogger and returns a never-matching regex ('(?!)'),
so a broken pattern degrades that one formatting feature instead of
trapping. Pattern properties stay non-optional, so no call-site churn
across ChatMessageFormatter, MessageTextHelpers, and
ChatComposerCoordinator.
The compile-time guarantee try! provided moves into tests: each
production pattern is asserted to compile and match a known-good sample,
so a typo in a pattern now fails CI instead of crashing users.
Part of #645 (the remaining try! sites; NoiseSessionManager's
force-unwrap is addressed separately in #1456).
* Normalize nicknames to Unicode NFC at storage and comparison boundaries
A nickname containing an accent can arrive in two canonically equivalent
but bytewise different forms: precomposed (U+00E9) or decomposed
(e + U+0301), depending on the keyboard and platform that produced it.
Nicknames were stored and compared without normalization, so visually
identical names silently failed to match: mentions of your own name did
not highlight or notify, /msg and /block could not resolve the peer,
autocomplete skipped candidates, and geohash DM resolution failed (#214).
Fix by canonicalizing to NFC (String.normalizedNickname) at every
boundary where a nickname enters storage - own nickname (ChatViewModel
didSet, alongside the existing trim), verified announce ingest
(BLEPeerRegistry), geohash presence (LocationPresenceStore), and
InputValidator.validateNickname - and by normalizing both sides at
comparison sites that can still see pre-normalization data (persisted
favorites, message-content mentions): peer resolution in
UnifiedPeerService and ChatPeerIdentityCoordinator, the three mention
checks, and autocomplete prefix matching.
The wire codec (AnnouncementPacket) is deliberately untouched: announces
are signature-verified against raw bytes, so canonicalization happens at
the storage layer, never during parsing.
Fixes#214