Restructured ContentView layout to prevent sidebar from covering input box
and removed gesture conflicts that caused jitter during slow drags.
Changes:
- Moved sidebar overlay to only cover messages area, not input box
- Input box now always accessible below sidebar (not covered by overlay)
- Removed blocking drag gesture from mainChatView
- Changed sidebar gesture from simultaneousGesture to gesture for priority
- Removed animation-disabling transactions that amplified touch noise
- Removed 2pt threshold checks that caused visible jumps
Result: Send button taps immediately, sidebar slides smoothly without jitter.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Location notes: fix performance and UI issues
Performance fixes:
- Add Set-based duplicate detection (O(1) vs O(n) lookup)
- Eliminates lag when receiving 200+ notes during EOSE
Correctness fixes:
- Fix optimistic echo timestamp to match signed event timestamp
- Add echo IDs to noteIDs set for consistency
UI improvements:
- Remove duplicate "loading recent notes" text in header
- Simplify toolbar icon color logic for immediate green indication
- Icon now reliably turns green when notes exist in geohash
Tests: All 3 LocationNotes tests passing
* Location notes: add remaining robustness fixes
Additional improvements:
- Align counter/manager limits to 200 (prevents showing count higher than displayable)
- Set loading state before clearing notes to eliminate UI flicker on geohash change
- Add geohash validation for building-level precision (8 valid base32 chars)
- Add defensive 500-note memory cap with automatic trimming
- Clear stale notesGeohash state on sheet dismiss
Tests:
- Fix test geohashes to use valid base32 characters
- All 3 LocationNotes tests passing
---------
Co-authored-by: jack <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>
Problem:
- Ghost peers from yesterday appeared on app restart
- Old messages resurfaced after restarting
- Peers running 24+ hours synced stale data to restarting peers
Root causes:
1. GossipSyncManager stored packets indefinitely (no time limit)
2. handleAnnounce/handleMessage had no timestamp validation
3. Relayed announces from other peers could be arbitrarily old
Solution (defense in depth):
- Added 15-minute age window to GossipSyncManager config
- Gossip storage rejects expired packets at ingestion
- Gossip sync responses filter out expired packets
- GCS payload building excludes expired packets
- Periodic cleanup removes expired announcements/messages
- handleAnnounce rejects stale announces before processing
- handleMessage rejects stale broadcast messages before processing
This prevents ghost peers from appearing on restart and ensures
only recent mesh state is synchronized between peers.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Fix test suite peer ID collisions
Use unique peer IDs for each test suite to prevent global registry
collisions when Swift Testing runs suites in parallel.
- PrivateChatE2ETests: PRIV_* prefix
- PublicChatE2ETests: PUB_* prefix
- Update all peer ID references to use actual instance IDs
This fixes the race condition where simplePublicMessage() was receiving
duplicate deliveries due to registry contamination.
* Add Bluetooth permission & state alerts on launch and foreground
Wire up existing Bluetooth alert infrastructure to show notifications
when Bluetooth is off, unauthorized, or unsupported.
Changes:
- Add didUpdateBluetoothState() to BitchatDelegate protocol
- BLEService now notifies delegate when Bluetooth state changes
- ChatViewModel implements delegate method to show alerts
- Check Bluetooth state on app launch (after 100ms delay)
- Check Bluetooth state when app comes to foreground
- Add getCurrentBluetoothState() method to BLEService
The UI alert already existed but wasn't wired up. Now users will see
appropriate alerts for:
- Bluetooth turned off
- Bluetooth permission denied
- Bluetooth unsupported on device
Alert includes a button to open Settings on iOS.
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Use unique peer IDs for each test suite to prevent global registry
collisions when Swift Testing runs suites in parallel.
- PrivateChatE2ETests: PRIV_* prefix
- PublicChatE2ETests: PUB_* prefix
- Update all peer ID references to use actual instance IDs
This fixes the race condition where simplePublicMessage() was receiving
duplicate deliveries due to registry contamination.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
- Add NetworkActivationService to permit Tor/Nostr only when location is authorized OR at least one mutual favorite exists.
- Gate TorManager.startIfNeeded/ensureRunningOnForeground behind a global allowAutoStart flag.
- Always stop Tor on background for deterministic restarts; rebuild sessions on foreground when allowed.
- NostrRelayManager respects the gate in connect/ensureConnections/subscribe/send/connectToRelay and skips reconnection when disallowed.
- Symmetric shutdown when conditions become disallowed: disconnect relays and stop Tor.
- Fix double-start by avoiding restart if Tor is already ready; prevent background thrash.
- Improve UX: post "starting tor…" via TorWillStart, and "tor started…" on initial ready; keep existing restart messages.
Rationale: Avoid starting Tor/relays when the user has no location permission and no mutual favorites, and ensure a clean, predictable lifecycle (no stale sockets, no double starts).
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* UI: replace textual 'close' with X icon\n\n- AppInfoView (iOS): use xmark icon in nav bar to match Location Notes style.\n- LocationChannelsSheet: use xmark icon for close on iOS/macOS toolbars; add accessibility label.
* Location Notes: prefix usernames with @ and lighten #geohash\n\n- Show @ before usernames in notes list.\n- Split header into '@' and '#geohash' and color the geohash with secondary green for consistency.
* Header spacing: add breathing room between channel badge, notes button, and people count\n\n- Add trailing padding after #mesh/#geohash badge.\n- Add leading padding before notes button and people counter to improve readability.
* Header: nudge #mesh/#geohash badge right with leading padding
* Location Notes + Header polish\n\n- Header: add space in '@ #geohash' and use darker green for geohash.\n- Notes list: render '@name#abcd' with darker green for #abcd to match chat.\n- Header: move geochat bookmark icon after #geohash badge with consistent spacing.
* Location Notes: @name regular green, #abcd darker; nudge #hash\n\n- Render '@' and base name in regular green, suffix '#abcd' in darker green.\n- Add extra left padding before '#geohash' in notes header.\n- Increase leading padding for channel badge to push #mesh/#geohash further right.
* Fix notes icon color: subscribe/count at block-level geohash\n\n- Use block (precision 7) geohash for notes: when opening sheet, on channel changes, and when subscribing the counter.\n- Aligns with LocationNotesManager which publishes at street-level geohash, allowing the counter to detect notes and turn icon blue.
* Header spacing: move #mesh/#geohash closer to notes/bookmark\n\n- Reduce trailing padding on channel badge and leading padding on notes/bookmark to cluster them together.\n- Keeps larger gap before people count for readability.
* Notes: standardize on building-level (8 chars) for publish/read\n\n- Use .building geohash when opening notes, reacting to channel updates, and subscribing the counter.\n- Update comments to reflect building-level scope.
* Location Channels sheet: use black sheet background like other sheets\n\n- Add backgroundColor and apply to container and list.\n- Hide list default background with scrollContentBackground(.hidden).
* Notes icon: use green when notes exist (matches app green)
* Location Channels: boxed 'bookmarked' section; keep 'remove location access' outside box\n\n- Wrap bookmarked list in a rounded, subtle grey box within the list.\n- Ensure the 'remove location access' button is not inside the box and clears list row background.
* Notes counter UX: avoid grey flicker when closing sheet\n\n- Preserve last count during resubscribe to prevent brief 0 state.\n- Keep existing subscription when building geohash temporarily unavailable; only cancel if none or permission revoked.
* Notes counter: unsubscribe without clearing count on resubscribe\n\n- Avoid calling cancel() in subscribe; just unsubscribe old sub to prevent wiping count to 0.\n- Prevents green→grey flicker after closing sheet or location updates.
* Notes icon: use sheet count until counter finishes initial load; don’t zero on sheet close\n\n- Compute hasNotes using max(count, sheetCount) while initialLoadComplete is false.\n- Remove sheetNotesCount reset on sheet disappear to avoid transient grey.
* Location Notes header: remove extra gap before #geohash (drop leading padding)
* Notes sheet: color #abcd suffix as darker green via opacity (match chat)
* Notes sheet: show timestamp in brackets; drop #abcd from @name
* chore: commit remaining local changes
* Header: move notes + bookmark to left of #mesh/#geohash
* Header spacing: tighten gap between #mesh/#geohash and peer count
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Location notes: Matrix loader with 1–3s minimum display; allow multiple notes; wire notes UI from mesh toolbar
* Location notes: prevent duplicate loading/subscriptions (StateObject manager, geohash captured at open, guard subscribe)
* Location notes: fix duplicate state redeclaration in ContentView
* Location notes: render note bodies with monospaced font in notes list
* Location notes: add close (x) button in sheet header (upper-right) using @Environment(\.dismiss)
* Location notes: acquire geohash on open (force refresh) and show Matrix loader until block-level geohash resolves; add LocationNotesSheet wrapper
* Location notes: inline sheet wrapper to avoid missing file in target; force location refresh and show Matrix loader until block geohash resolves
* Location notes: remove Matrix animation; use simple spinner when acquiring location; simplify notes view layout
* Location notes: remove per-note relative timestamp from sheet (no seconds display)
* Location notes: fix intermittent first load by ensuring resubscribe after geohash change, removing over-eager subscribe guard, and widening note fetch window (no since filter)
* Location notes: add background counter service and show count next to notes icon on mesh; auto-subscribe to current block geohash
* Location notes: move notes icon to the right of #mesh badge in toolbar
* Location notes: restore timestamps in notes list; remove loading state from manager and sheet (no spinner/animation)
* Location notes: drop 'teleport' tag from kind-1 events; simplify note builder API and usage
* Location notes: show timestamp as relative within 7 days, else absolute date (MMM d or MMM d, y); keep monospaced styling
* Location notes: append 'ago' to relative timestamps (within 7 days)
* Switch location notes and UI helpers to building-level geohash (precision 8): add GeohashChannelLevel.building, map length 8, use building for notes selection and counter, add building name mapping
* Location notes: change notes toolbar icon to SF Symbol 'long.text.page.and.pencil'
* Revert notes geohash selection back to block-level (precision 7); keep building level available but unused; update counter and sheet accordingly
* Location notes: show block name (from reverse geocode) in header instead of 'street-level notes'
* Nostr: add EOSE handling with callback support for subscriptions; wire to LocationNotesManager/Counter (initialLoadComplete). Remove 'building' level from channel enum and geocoder mapping; geohash list shows block/neighborhood/city/province/region only
* Fix Swift 6 isolation: hop to @MainActor inside Timer callback for EOSE tracker mutations
* Notes counter: show 0 by default; subscribe at building-level (precision 8) for notes and counter; keep building hidden in channel list
* Notes header: show building name when available (fallback to block name)
* Notes: subscribe counter to building + parent block (merge results); hide notes icon unless location is authorized; replace 10s timer with live location updates while sheet open
* Notes counter: subscribe only to building geohash (precision 8) so count reflects current 8-cell; auto-begin live location refresh on mesh (and permission) to update as you walk
* Notes header: show count in parentheses; icon shows blue if any notes, grey if none; only start live location updates while sheet is open; reduce nickname width; set live distance filter to 10m
* Notes header: show count as '(N note/notes)' with non-bold, secondary styling next to title
* Notes header: move count before title as '<N> note(s) @ #gh'; remove parentheses; keep count non-bold
* Notes header: bold count text; ensure sheet updates when building geohash changes by calling manager.setGeohash on geohash change
* Notes sheet: add light haptic feedback when building geohash changes (iOS only)
* Notes icon: force a one-shot location refresh before (re)subscribing the counter so icon turns blue immediately on current 8-cell
* Notes subscribe: fallback to default relays when GeoRelayDirectory has no entries (pass nil relayUrls) so counter/icon turn blue and sheet loads even before georelay fetch
* Notes icon: turn blue immediately when sheet shows notes by passing count up from sheet (closure) and OR-ing with counter; reset on sheet close
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Favorites: mesh-only system message; stop reconnect resends; gate system on state change
* Favorites: remove npub resend tracking and nickname-based key migration; rely on Noise key as identity
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Fix emote targeting and grammar; add tests
Prevent 'system' mis-target via peerID-derived display name in actions sheet. Correct /hug and /slap usage/error grammar by passing base command name. Improve geohash nickname resolution to match displayName with #suffix. Add CommandProcessor tests.
* Geohash ordering: strict in-order inserts and timestamp clamp
Use channel-aware late-insert threshold with 0s for geohash to keep strict chronological order. Clamp future Nostr event timestamps to 'now' to avoid future-dated items skewing order in geohash timelines.
* Trim trailing/leading spaces in geohash nicknames and tag emission
Sanitize Nostr 'n' tag values on ingest and emit by trimming whitespace/newlines to prevent trailing spaces in displayed usernames. Local nickname is already trimmed on focus loss and submit.
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* feat(tor): Tor-by-default scaffold and integration
- Add TorManager with static/dlopen start, torrc generation, SOCKS probe
- Add TorURLSession; route Nostr/Web fetches via SOCKS proxy
- Add chat system messages for Tor status; show progress (macOS) and ready
- Disable ControlPort bootstrap monitor on iOS; keep it on macOS
- Make Tor waits non-blocking; avoid main-actor stalls on startup
- Queue & flush Nostr subscriptions on relay connect; skip duplicates
- Always rewrite torrc at launch to fix iOS container path mismatches
- Link libz; add project wiring for tor-nolzma.xcframework
- Minor fixes: SOCKS probe resumeOnce guard, entitlement for network.server (macOS)
* iOS: deterministic Tor recovery + 100% gating; BLE-first; session rebuild
- Restart/wake Tor on foreground via ControlPort (ACTIVE/SHUTDOWN),
avoid restarts during bootstrap; add NWPathMonitor to trigger checks
- Use NWConnection control polling for GETINFO; remove blocking CFStream
readers to avoid QoS inversions; compute readiness from SOCKS + 100%
- Rebuild TorURLSession on resume; reset Nostr connections to rebind
- Gate all internet after full bootstrap; keep BLE mesh startup fast
- Fix Swift 6 capture issues; hop UI updates to @MainActor
- Remove Tor progress spam; persist initial "starting tor..." system message
* UI: show Tor system messages only in geohash channels (not mesh)
- Gate "starting tor..." and readiness/timeout messages to geohash view
- Add helper addGeohashOnlySystemMessage() to avoid posting to mesh timeline
- Persist system messages in geohash backing store via addPublicSystemMessage()
* Relays: treat repeated -1011 handshake failures as permanent; skip reconnects
- Classify NSURLErrorBadServerResponse as permanent and stop retrying
- Filter permanently-failed relays from subscribe/connect attempts
- Avoid reconnect scheduling for permanently failed relays
* Embed Tor via tor_api; deterministic restart + Nostr gating; add Tor notifications
- Run Tor via tor_api in a dedicated thread with OwningControllerFD
- Cleanly stop Tor on background; restart on .active (single instance)
- Avoid fallback to tor_main/dlopen; add is-running check to prevent duplicates
- Fix argv lifetime in C glue to avoid strcmp crash on start
- Gate Nostr connect/subscribe/send until Tor is fully ready
- Rebuild URLSession + reset relays after Tor readiness (scene-based)
- Remove TorDidBecomeReady double-reset and appDidBecomeActive resubscribe
- Add TorWillRestart/TorDidBecomeReady notifications and chat system messages
- Debounce path-change restarts; ACTIVE poke first; coalesce subs; cancel stale reconnect timers
- Project: add CTorHost.c and TorNotifications.swift to targets; fix libz.tbd path
* Defer Nostr setup logs until Tor is ready; fix subscribe coalescing and reconnect generation
- Move "Connecting to Nostr relays" log after awaitReady()
- Log "Queuing subscription" when Tor not ready; only coalesce when handler exists
- Clear coalescer on unsubscribe
- Cancel stale reconnect timers using connectionGeneration
- Remove app-level TorDidBecomeReady reset to avoid duplicate reconnects
- Debounce path-change restarts
* Gate Nostr init/subscription logs until Tor is ready
- ChatViewModel: await Tor readiness before initializing Nostr and logging
- Only log GeoDM subscription when Tor is ready to avoid early noise
* Make Nostr connect single-sourced; defer DM subscription until connected
- Remove duplicate connect call from ChatViewModel; let scene-based flow connect
- Setup DM subscription once on first connection via sink
- Reduce early subscription send/cancel noise after Tor restarts
* On launch, queue Nostr subscriptions without initiating connects; let centralized connect handle it
- In subscribe(), if no connections exist, just list relays and queue subs
- Avoids early send/cancel churn before connect() runs post-Tor-ready
* Always queue subscriptions and flush on connection; avoid immediate sends
- Prevents early send/cancel churn at launch and during reconnects
- If relays are already connected, flush immediately; otherwise pending until connected
* UI: scope Tor restart messages to geohash channels; skip initial foreground restart on cold launch to avoid confusing system message in #mesh
* geo: disable background sampling + notifications\n- Gate sampling to foreground only (beginGeohashSampling, watchers)\n- Suppress geohash activity notifications unless app is active\n- Stop sampling explicitly on background scene phase
* Update BitchatApp.swift
Co-authored-by: asmo <asmogo@protonmail.com>
* Update BitchatApp.swift
Co-authored-by: asmo <asmogo@protonmail.com>
* Update BitchatApp.swift
Co-authored-by: asmo <asmogo@protonmail.com>
* Update bitchat/BitchatApp.swift
Co-authored-by: asmo <asmogo@protonmail.com>
* Update bitchat/BitchatApp.swift
Co-authored-by: asmo <asmogo@protonmail.com>
* fix(iOS App): resolve merge artifacts in scenePhase handler\n- Remove duplicate didEnterBackground state\n- Fix switch/if braces and logic for foreground restart gating
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: asmo <asmogo@protonmail.com>
* Screenshot privacy: warn on location sheet; gate screenshot broadcasts to chat only\n\n- Track sheet presentation in ChatViewModel\n- Show privacy alert when screenshot taken on location channels\n- Ignore screenshots for App Info and Location sheet for broadcast\n- ContentView wires sheet onAppear/onDisappear and presents alert\n- Fix location sheet subtitle wrapping: render as single Text to avoid orphaned bullets
* Fix duplicate messages after channel switch\n\n- Dedup on per-geohash append (skip if ID exists)\n- Dedup and sort timeline by timestamp when switching into a geohash\n- Keep content/layout unchanged; add lightweight debug logs for empty rows
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Add geohash bookmarks: persistence, sampling integration, and UI\n\n- Add GeohashBookmarksStore with UserDefaults persistence and toggle API\n- Sample union of regional + bookmarked geohashes for activity notifications\n- LocationChannelsSheet: bookmark icons on nearby rows and a Bookmarked section\n- Header toolbar: toggle bookmark for current geohash\n- Tests: GeohashBookmarksStoreTests for normalization and persistence\n\nRationale: Bookmarked geohashes are always sampled for new activity notifications and quickly selectable from the sheet.
* Notifications: remove background 'new chats!' nudge; geohash activity only on zero→alive; mesh 'nearby' only on zero→alive\n\n- Geohash sampling: notify only when previous participant count in last 5m was zero, with 30s freshness gate\n- Suppress old sampled events after (re)subscribe\n- Remove channel inactivity nudge\n- Mesh: reset rising-edge gate immediately when peer list goes empty (strict zero→alive)
* Geohash notifications: ensure triggering message appears\n\n- On zero→alive sampling, pre-populate geoTimelines[gh] with the triggering message\n- Dedup on per-geohash timeline and UI messages by message ID to avoid duplicates after subscribe\n- Keeps participant update, block/self checks, and cooldown intact
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
- Remove "new chats!" background nudge; notify only when a regional geohash goes from 0 participants to activity, with content preview and cooldown.\n- Do not mark teleported when selected geohash is in regional channel list; clear persisted teleport when in-region; avoid self teleported state from historical tags; deep links don’t mark teleported for regional geohashes or during cold start.\n- Sort per-geohash timelines chronologically on re-entry and trim whitespace-only content to prevent blank lines.\n- Trim inbound public content and ignore whitespace-only sends.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
- Use minimal-distance hue palette for mesh and geohash lists; align chat sender colors with list palette.
- Add foreground geo notifications for different channels; deep-link to bitchat://geohash/<gh>; per-geohash 60s cooldown; respect self/blocks.
- Global geohash sampling runs outside the sheet; delegate handles deeplinks and suppresses when already in-channel.
- Switch location channel sheet to continuous CoreLocation with 21m distance filter; add config knob.
- Only link geohash hashtags when standalone (no @name#abcd or word#abcd).
- Include mesh-reachable peers in mesh counts and in "bitchatters nearby" notification.
- Add TransportConfig knobs for palette and geo notifications.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Sign public broadcasts; verify relayed messages via persisted signing keys; keep scheduled relays in sparse graphs and speed their jitter; persist announce signing key for offline auth; add short backoff after disconnect errors to reduce reconnect thrash
* Add connected vs reachable model: retain peers after link drop, expire after reachability window; expose all peers in snapshots; compute isReachable in UI; add meshReachable state and sorting; avoid removing peers on link events; notify UI on stale removals
* ContentView: handle new .meshReachable connection state in header icon switch (exhaustive switch fix)
* Logs: tag relayed announces as 'Reachable via mesh' and annotate public message logs with (direct|mesh) path for easier field analysis
* Fix syntax error: remove stray else/log inserted into writeOrEnqueue; keep logs clean
* UI: use 'point.3.connected.trianglepath.dotted' for mesh-reachable; change people count to include connected+reachable (exclude Nostr-only)
* UI: switch to 'point.3.filled.connected.trianglepath.dotted' for mesh-reachable icons in list and header
* Reachability: reduce retention to 21s for all peers (verified and unverified) to minimize stale presence
* mesh DMs/acks: route to reachable peers; queue READ/DELIVERED until handshake; add Transport.isPeerReachable; UI: hide offline non-mutuals; DM header: better name fallback + show transport + encryption icons; fix NostrTransport conformance
* Verification sheet: compute encryption status and fingerprint using short mesh ID mapping (fix 'not encrypted/handshake' for DMs with stable key)
* Announce cadence: faster discovery (4s), sparse 15±4s, dense 30±8s; initial 0.6s; post-subscribe 50ms; min-force 150ms; maintenance 5s; proactive announces on handshake + recent-traffic nudge
* Relay: increase broadcast TTL cap in sparse graphs to 6; tighten jitter for handshake (10–35ms) and directed (20–60ms) relays
* Range/robustness: store-and-forward for directed packets (15s) with flush on new links + periodic; announces: no subset + afterglow re-announce on first-seen; adaptive scanning: force ON when <=2 neighbors or recent traffic
* Fix warnings: remove unused msgID and unused mutable var in directed spool flush
* Announces: TTL 7 (sparse only) via RelayController; no fanout subset for announces; neighbor-change rebroadcast of last 2–3 announces. Fragments: faster pacing (5ms global, 4ms directed).
* Peer list: real-time icon updates by publishing snapshots on connectivity checks; add unread message indicator (envelope) next to peers with unread DMs
* UI: unread envelope uses orange; hasUnreadMessages checks Nostr conv key for peers with known Nostr pubkeys (geohash DM consistency)
* Logs/robustness: debounce disconnect notifications (1.5s), debounce 'reconnected' logs (2s), add weak-link cooldown after timeouts on very weak RSSI (<= -90)
* Peer icons: faster, accurate reachability\n\n- Run connectivity checks every maintenance tick (5s)\n- Publish peer snapshots on central unsubscribe for instant UI refresh\n- Lower inactivity timeout to 8s and disconnect debounce to 0.9s\n- Gate reachability on mesh-attached (>=1 direct link); no links => no reachable peers\n- Keep 21s retention for verified/unverified, but only when attached to mesh\n\nImproves list responsiveness when walking out of range and prevents stale 'reachable' states when isolated.
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Remove dead code and artifacts: drop PeerManager, unused views/types; delete LegacyTestProtocolTypes; update .gitignore; purge TestResult.xcresult and build.log
* Tests: gate verbose prints under DEBUG; ChatViewModel: remove legacy fingerprint helper and rely on UnifiedPeerService
* Share Extension: migrate to UIKit + UTTypes; drop Social/SLComposeServiceViewController
* Remove 'preparing to share …' system message; send shared content immediately
* Inline comment cleanup: drop legacy 'removed' breadcrumbs across protocols, services, view model, and views
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Improve BLE mesh relay and flooding
Add last-hop suppression using ingress-link tracking to prevent echo. Implement deterministic always-relay for handshakes and directed encrypted/fragments; widen jitter for broadcasts; keep TTL cap only for broadcast. Add deterministic K-of-N broadcast fanout to reduce amplification in dense topologies. Introduce backpressure-aware writes using canSendWriteWithoutResponse with per-peripheral queues and draining on peripheralIsReady. Minor helpers for messageID, deterministic selection, and maintenance cleanup.
* Tests: stabilize FragmentationTests and InputValidatorTests
Make _test_handlePacket mark synthetic peers verified/connected with normalized senderID to avoid drops in public-message reassembly tests. Tighten validatePeerID to reject non-hex strings when length equals 16 or 64; allow internal IDs only for other lengths. All iOS simulator tests pass locally.
---------
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>
* NIP-17/44: adopt NIP-44 v2 (XChaCha20-Poly1305, v2: base64url), switch rumor kind to 14, randomize BIP-340 aux; add XChaCha20Poly1305Compat and wire-up
* Nostr DMs: ensure delivered/read acks are sent even without Noise key mapping by falling back to direct Nostr (geohash-style) acks to sender pubkey
* NIP-44 v2 decrypt: try both Y parities for x-only sender pubkeys (even then odd) to fix unwrap authentication failures
* Tests: add NIP-44 v2 ACK round-trip tests (delivered/read) using bitchat1 embedding and gift-wrap decrypt path
* UI: fix DM autoscroll IDs by using dm:<peer>|<id> for private chat item IDs and preserve anchors, ensuring scrollTo targets exist when opening/switching/new messages
* UI: fix string interpolation in DM/geohash context keys (remove escaped interpolation) to resolve 'unused ch' warnings and ensure proper IDs
* UI: MeshPeerList shows transport state icon: radio for mesh-connected, purple globe for mutual favorite/Nostr; fallback dim person for others
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* feat(notifications): unify DM notifications across transports; notify for unread+recent even in foreground; delegate suppresses if chat open
* fix(geohash): include own past geohash messages on channel load; skip only very recent self-echo (<15s) to avoid duplicates
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* feat(georelay): route geohash kind 20000 via nearest relays; add GeoRelayDirectory; target geohash subscriptions; avoid duplicate connections
* feat(georelay): fetch daily from remote CSV with fallback to bundled; cache to app support; prefetch on app load
* fix(georelay): make CSV parser nonisolated and call as Self.parseCSV to satisfy Swift 6 actor isolation
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Remove link previews: link URLs inline\n\n- Delete LinkPreviewView and all references\n- Strip preview usage from ContentView\n- Make detected URLs tappable via .link attribute\n- Add MessageTextHelpers for tokens/length checks\n- Wire helpers into iOS/macOS targets
* Peer list UX: match message colors, use map pin icon, stable ordering\n\n- Color list names and icons with same peer color as messages\n- Default icon mappin; teleported uses face.dashed (iOS)\n- Geohash: move teleported peers to bottom\n- Maintain stable order: append new peers, remove missing\n- Mesh list: replace state icons with colored mappin/person
* Fix peer list build errors: remove ambiguous Set type, avoid guard in ViewBuilder, remove duplicate bindings, and simplify person lookup
* Peer list: remove onChange expressions to avoid type-check issues; rely on ObservedObject updates
* Peer lists: simplify view structure to satisfy SwiftUI type checker (replace Group with VStack, flatten body)
* Peer lists: move ordering mutations to onAppear/onChange, keep body pure; fix result builder errors
* Fix: remove trailing modifiers outside if/else to avoid ViewBuilder type issues
* Color parity: fix seed cache to include color scheme; normalize Nostr seeds; switch to mappin.circle icon
* Peer colors: ensure exact match with message colors\n\n- Mesh list uses same seed logic as messages (getNoiseKeyForShortID fallback)\n- Color cache keyed by theme; normalize nostr seeds\n- Icons: use mappin.and.ellipse (default) and face.dashed for teleported\n- Teleport tag: accept "teleported" in addition to "teleport" and presence events
* Fix geohash color mismatch: populate nostrKeyMapping before building messages in resubscribe; also honor t,teleport tag for teleported state
* Message actions: username tap opens sheet via clickable AttributedString; message tap inserts @mention; rename to 'direct message'
* Cashu UX: suppress 'Show more' when message contains a Cashu token (chips handle rendering)
* Cashu detection: broaden regex (allow '.') + shorter min length; avoid long-text fallback when Cashu present so chips render and no full blob
* Geohash levels: rename region->province, country->region; update labels, precision, UI, manager mapping, tests; add Codable backward-compat for renamed cases
* Fix braces in LocationChannel.swift: close enum before extension to satisfy file-scope declarations
* Geohash links: make #geohash tappable and open that channel (bitchat://geohash/<gh>); handle in ContentView.onOpenURL with validation and selection
* Underline tappable #geohash mentions for clarity
* Geochat: sanitize timeline on channel switch to remove any blank-content entries (prevents blank rows)
* Xcode project: sync sources after UI changes (remove LinkPreview, add helpers, update build refs)
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* fix(geo-block): enable block/unblock for geohash users and enforce blocks\n\n- Add persisted set of blocked Nostr pubkeys in SecureIdentityStateManager\n- Check Nostr blocks for incoming messages (public + geo DMs)\n- Prevent sending geo DMs to blocked users\n- Extend /block and /unblock to resolve geohash display names to pubkeys and act accordingly\n- Improve /block list to show geohash blocks (visible names or #suffix)
* fix(geo-block): enforce blocks on geohash public and DM receive; add block/unblock actions to geohash people list
* fix: mark handlePublicMessage as @MainActor to call isMessageBlocked safely
* ui(block): show blocked indicator (nosign icon) next to blocked peers in mesh and geohash lists
* fix(geo-block): early-drop blocked pubkeys on geohash receive; filter blocked users from participants list
* fix(geo-block): purge existing geohash messages/DMs on block; block directly from chat using Nostr sender ID when available
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* feat(cashu): auto-link cashu tokens in chat
- Detect cashuA/cashuB tokens via regex and render as tappable links
- Style like URLs (underline; blue for others, orange for self)
- Add openURL handler for cashu: scheme to delegate to wallet on iOS
- Respect existing heavy-content gating and long-message collapsing
* feat(ln): auto-link Lightning invoices and LNURL + lightning: scheme\n\n- Detect BOLT11 (lnbc/lntb/lnbcrt...), LNURL bech32, and lightning: URIs\n- Render as tappable links with lightning: scheme; consistent styling\n- Handle lightning: in openURL alongside cashu:
* feat(links): replace raw Cashu/Lightning tokens with compact chips (🥜 pay via cashu / ⚡ pay via lightning) while keeping them tappable
* style(links): add subtle background highlight to cashu/lightning chips
* ui(links): render Lightning/Cashu as rounded chips under message with padding; remove inline chip text
* ui(links): increase chip padding and corner radius; add extra top padding for chip row
* scroll: auto-scroll to bottom when sending a new message (public + private), regardless of current scroll position
* scroll(geo): when switching geohashes, scroll to top of chat (first visible message)
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Feature: assign stable per-peer colors (non-self) across mesh/geohash/DM; keep self orange and mentions-to-me orange; cache colors
* Fix compile warnings: remove unused primaryColor in formatMessageAsText; remove unused CryptoKit import; mark peerColor/formatMessageAsText @MainActor to call main-actor helpers
* Fix: re-import CryptoKit for SHA256 usage in ChatViewModel
* Peer lists: apply same per-peer colors as chat (self orange); suffix uses lighter variant; geohash uses Nostr pubkey, mesh uses Noise key
* Fix warning: remove unused peerNicknames in MeshPeerList
* UI: add 'mention' action in message actions to prefill input with @nick#abcd and focus input
* UX: long-press a message to prefill @mention with sender's full nick#abcd and focus input
* UX: remove long-press mention; add context menu 'Copy message' that copies only the message body (no nick/timestamp)
* Geo teleported: robust tag detection (accept 't', 'teleport', and boolean-like values); mark self on send when tagging; should fix peer list icons
* Logs: add GeoTeleport diagnostics (incoming tags, self/peer marking, counts) to debug peer list dashed icon behavior
* Teleport persistence: store per-geohash teleported state in UserDefaults; initialize on startup; OR with location-derived status; sheet writes persisted flag on select/teleport
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Scroll UX: auto-scroll only when last item visible; preserve user position when scrolled up for mesh/geohash/DM; reduce blanking after very long messages
* iOS: re-enable keyboard autocomplete and default capitalization for message input
* Styling: stop blue/underline styling for #hashtags; render as normal text color (self=orange, others=green)
* Geo UI: ensure self shows teleported (face.dashed) if either per-session tag or manager flag is true
* Geo teleported: publish UI updates by assigning @Published Set instead of in-place insert; update on tag receipt and channel switch
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Geohash peers: show face.dashed for self when channel selected via teleport; face.smiling otherwise
* Geo presence: broadcast 'teleport' tag on geochat join; track teleported participants and show face.dashed for them in peer list
* Teleport tag: attach to actual geohash chat events (sendMessage, emotes, screenshots) instead of separate presence; remove presence emit
* Fix: show face.dashed for any teleported peer (not just self) in geohash list
* Geo list: show self immediately on channel switch and mark teleported state; clear teleported flags on leaving geochat
* Teleport persistence: recompute teleported based on current location vs selected geohash; add face.dashed icon to Teleport button label
* Styling: use lighter green/orange for #abcd suffix after nicknames in all chats (senders and @mentions)
* Peer lists: render #abcd suffix as lighter green/orange (self orange) in geohash and mesh lists
* Toolbar: move unread icon next to #channel badge and allow dynamic width; Peer lists: increase top spacing before first item
* Toolbar: prevent geohash channel badge from truncating; give it layout priority and fixed width
* Toolbar: make unread envelope independent from channel button (sits left of badge); fix accidental taps opening channel selector
* Toolbar: make unread envelope open most recent unread/private chat directly
* Geohash peer list: render self row fully orange (icon, base, suffix, '(you)')
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
* Remove "street" location channel; add coverage + names; style mesh
- Drop GeohashChannelLevel.street and update mappings/tests\n- Map geohash lengths >=8 to Block in teleport, no Street\n- Show ~distance coverage (mi/km via Locale.measurementSystem) next to each #geohash\n- Add reverse geocoding to display coarse names (country/region/city/neighborhood) per level\n- Style "#mesh" row title with toolbar blue\n- Fix iOS 16 deprecation: use Locale.measurementSystem
* Project: update Xcode project (auto)
* UI: use '~' without trailing space before location name in sheet
* Location sheet: poll for location at regular intervals while open (replace significant-move updates)
* UI: show Bluetooth range next to #bluetooth in location sheet
* UI: remove leading '#' from mesh title in location sheet
* UI: bold location name in sheet when geohash has >0 people; refactor row to render subtitle pieces
* UI: bold mesh/level titles when participant count > 0; factor meshCount()
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>