Mechanical style fixes across the enabled rule set, mostly via
swiftlint --fix (trailing_comma, comma, colon, trailing_newline,
comment_spacing, unused_closure_parameter, unneeded_break_in_switch,
opening_brace) plus hand fixes:
- non_optional_string_data_conversion (45): .data(using: .utf8)! and
?? Data() fallbacks replaced with the non-optional Data(_.utf8),
including two production sites (NIP-44 HKDF info constant and the
announce canonicalization context/nickname bytes — byte-identical
output, only the impossible-nil handling is gone).
- switch_case_alignment: LocationChannel had a misindented closing
brace; also repaired an --fix artifact in BLEService's .none case.
- redundant_string_enum_value: TrustLevel raw values equal to the case
names (encoded form unchanged).
- unused_optional_binding: let _ = binds replaced with != nil / is Bool.
- static_over_final_class: PreviewView.layerClass.
- Resolved the BinaryProtocolTests TODO by documenting that 8-byte
recipient ID truncation is the fixed wire-field size, not a bug.
The 4 remaining violations are all todo markers for a shared
test-helpers module (tracked in #1088) and one Reuse note.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Centralize UI theme colors into semantic ThemePalette tokens
Introduce AppTheme/ThemePalette (Utils/Theme.swift) with an environment
key and @ThemedPalette property wrapper, persisted via @AppStorage.
Views now resolve background/primary/secondary/accentBlue/alertRed/
divider tokens from the environment instead of computing colors inline,
removing the backgroundColor/textColor/secondaryTextColor prop-drilling
through the header, composer, and people-sheet hierarchy.
Matrix theme output is pixel-identical; this is groundwork for a
user-selectable theme switcher.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add liquid glass theme with in-app appearance switcher
Add a .liquidGlass AppTheme alongside the matrix terminal theme,
selectable from a one-line APPEARANCE row in the app info sheet
(persisted via @AppStorage, applies live).
Liquid glass renders system fonts and colors over a subtle gradient
backdrop, with the header and composer floating as Liquid Glass panels
(real .glassEffect() on iOS/macOS 26, compiler-gated with an
ultraThinMaterial fallback for older SDKs). The message list scrolls
underneath the chrome via safe-area insets, and all sheets share the
same backdrop and surface language. The matrix theme is unchanged.
Details:
- Theme is threaded through ChatMessageFormatter so message
AttributedStrings switch font design per theme; the per-message
format cache gains a variant key so themes never serve each other's
cached strings
- New palette tokens: accent (interactive tint) and locationAccent
(geohash green), replacing scattered hardcoded greens/blues in the
voice note, waveform, verification, and people-sheet views
- Header controls get full-height tap targets and the people count
becomes a real Button (previously a tap gesture on the cluster)
- CommandSuggestionsView renders nothing when empty instead of a
zero-height view that pushed the composer input off-center
- New appearance strings localized for all 29 catalog languages
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Extract each type to a separate file
* Nostr ID Bridge: Convert static func/vars to instance
* `KeychainHelper` behind a protocol to easily mock
* Update tests with ID Bridge and MockKeychainHelper
---------
Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com>
The camera scanner was continuously detecting the same QR code (10-30+ times/second), causing multiple verification notifications to be sent. This happened because:
1. AVCaptureMetadataOutput fires repeatedly while QR is visible
2. Each detection triggered a new verification flow
3. After receiving response, pendingQRVerifications was cleared, allowing duplicate scans
Changes:
- Add deduplication using lastValid state to ignore re-scans of same QR code
- Only set lastValid after successful verification initiation
- Add onSuccess callback to close scanner after successful scan
- Automatically return to "My QR" view after verification starts
- Apply same logic to both iOS camera and macOS manual input paths
This ensures exactly one verification request per scan session.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Automated update of relay data - Sun Sep 21 06:26:33 UTC 2025
* chore(l10n): add empty string catalogs
* chore(l10n): populate string catalogs from legacy resources
* test(l10n): add catalog guardrail suite
* chore(l10n): remove legacy localization files
* fix: Add localization resources to Package.swift targets
- Add .process("Localization") to bitchat target resources
- Add .process("Localization") to bitchatTests target resources
- Resolves Bundle.module resource loading for localization files
- Enables proper localization testing in Swift Package Manager builds
* feat: Add Korean localization and convert to UTF-8 format
Korean Language Support:
- Add complete Korean (ko) localization with 191 strings from PR #686
- Include all app strings: UI, features, system messages, alerts
- Include all share extension strings: status messages, errors
- Verified 100% translation coverage for Korean locale
UTF-8 Format Conversion:
- Convert 23,047 Unicode escape sequences to readable UTF-8 characters
- Transform \u sequences (e.g. \u0625\u063a\u0644\u0627\u0642) to native text (إغلاق)
- Improve maintainability across all 15 supported locales
- Preserve all existing translations while enhancing readability
Locales supported: en, ar, de, es, fr, he, id, it, ja, ko, ne, pt-BR, ru, uk, zh-Hans
* test: Enhance dynamic localization test framework
Dynamic Test Framework:
- Replace hardcoded locale tests with data-driven approach
- Add testLocalizationExpectedValues() for dynamic locale validation
- Add testConfiguredLocalesCompleteness() for coverage verification
- Tests now read configuration from PrimaryLocalizationKeys.json
Expanded Test Coverage:
- Increase from 14 to 33 key validations (135% increase)
- Add critical UI strings: common actions, app info, security, sharing
- Cover 364 total string validations across 15 locales
- Include Korean validation with native Korean expected values
Test Categories Added:
- Common UI: cancel, close, copy actions
- App Info: encryption, offline features, app name
- Bluetooth: permission and settings alerts
- Security: verification badges and actions
- Share Extension: all status and error messages
- Content Actions: accessibility and user actions
Maintains 100% test success rate across all supported locales.
* fix: Convert plural strings to correct xcstrings format
Three plural strings (content.accessibility.people_count,
location_channels.row_title, location_notes.header) were using
incorrect format causing runtime String(format:) errors.
Migrated from stringUnit.variations structure to proper
substitutions.variations format across all 14 languages.
* refactor(l10n): migrate to native String(localized:) APIs
Remove L10n.string wrapper in favor of Swift's native localization APIs.
Migrate 100+ localization call sites to use String(localized:) and String(localized:defaultValue:) with string interpolation.
- Update catalog to use interpolation syntax (\(var)) instead of format specifiers (%@)
- Migrate simple strings to String(localized:)
- Migrate strings with arguments to String(localized:defaultValue:) with interpolation
- Keep format strings for plural substitutions (String(format:locale:))
- Remove bitchat/Utils/Localization.swift
Net result: -407 insertions, +130 deletions across 15 files
* fix(l10n): correct interpolation to use format strings
Interpolation in String(localized:defaultValue:) doesn't work as expected -
the interpolation happens at the call site before localization lookup.
Convert dynamic strings to use String(format:String(localized:),args) pattern:
- Update catalog entries from \(var) syntax to %@ placeholders
- Wrap String(localized:) calls with String(format:locale:) for dynamic values
- Affects 17 strings across 6 files
This fixes UI showing literal "\(geohash)" text instead of actual values.
* chore(l10n): remove invalid catalog entries
Remove auto-extracted literal strings (@, #, ✔︎, @%@, bitchat/) that were
generated without proper localization structure. These caused test
decoding failures.
* fix(l10n): remove unused auto-extracted format string
Remove '%@/%@' key that was auto-extracted by Xcode but never used.
This key only existed in English causing locale parity test failures
across all 13 other languages.
Fixes locale parity tests - all 8 localization tests now pass with
only expected failures (incomplete translations in some locales).
* fix(l10n): copy format string to all locales for 100% completion
Add %@/%@ format string to all 14 non-English locales. Format strings
are locale-independent so using the same value everywhere is correct.
This brings all locales to 100% completion (189/189 strings) to prevent
Xcode from reporting incomplete translations when building.
* fix(l10n): prevent auto-extraction of UI literals
Use Text(verbatim:) for non-localizable UI elements:
- App branding ("bitchat/")
- Symbols (@, #, ✔︎)
- Dynamic usernames (@username)
- Count ratios (reached/total)
This prevents Xcode from auto-extracting these literals into the
String Catalog when building through Xcode GUI, which was causing
locales to show 96% completion instead of 100%.
* chore(l10n): remove auto-extracted UI literal entries
Delete 5 auto-extracted keys from catalog that are now using Text(verbatim:):
- @, #, ✔︎, %@, %@/%@
These were showing as stale/incomplete in Xcode causing 97% completion.
All locales now at 100% (188/188 strings).
* fix(l10n): prevent AttributedString from extracting @ symbol
Use string interpolation "\\(at)" instead of literal "@" in
AttributedString to prevent Xcode from auto-extracting it to the
String Catalog during build.
This was the last string causing locales to show 99% instead of 100%.
* fix(l10n): add %@ as non-translatable key in all locales
Mark %@ as non-translatable and add to all 15 locales with same value.
This prevents Xcode from showing incomplete translations when it
auto-extracts this format specifier during GUI builds.
All locales remain at 100% (189/189 strings).
* refactor: move Localizable.xcstrings to bitchat root
Move bitchat/Localization/Localizable.xcstrings to bitchat/ (after LaunchScreen)
and remove empty Localization directory.
* fix(test): update catalog path in localization tests
Update test paths from bitchat/Localization/Localizable.xcstrings to
bitchat/Localizable.xcstrings after moving the file.
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* QR verification scaffold: add Noise verify payload types, VerificationService with QR schema/signing, placeholder MyQR/Scan views, and UI entry points in header
* QR: fix VerificationQR mutability (sigHex var) and remove duplicate Data hex helpers to resolve redeclaration; wire signed payload assembly
* QR: render actual QR images with CoreImage; add copy button; keep scanner placeholder for now
* QR: fix SwiftUI modifiers — apply .interpolation(.none) and .resizable() to platform Image inside ImageWrapper; remove from wrapper usage
* QR: add iOS camera scanner using AVFoundation; integrate into Scan view; add NSCameraUsageDescription to Info.plist
* QR: make NoisePayloadType exhaustive in ChatViewModel switches by ignoring verifyChallenge/verifyResponse for now (placeholder)
* QR verification: speed + persistence + UX
- Inject live Noise into VerificationService; prewarm QR on app start
- Keep camera active; remove intermediate responder toast
- One-shot/dupe guards and deferred send on handshake
- Persist verified status immediately; standardize fingerprint (SHA-256)
- Show verified badge for offline favorites; mutual verification toast
- VERIFY sheet styling to match peer sheet; UI polish
- Logs to diagnose verified load + favorites mapping
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>