Conversation
Match the node ID styling pattern from MessageActionsSheet (c207ab8) — show the hex byte inline before the repeater name instead of on a separate line below it.
- prevent re-entrant diffable snapshot apply calls during scroll callbacks - queue snapshot updates to avoid UITableView deadlock crashes
- cover the update plus scroll-completion sequence that previously crashed - guard against regressions in chat diffable snapshot sequencing
- Deduplicate divider logic into computeDividerPosition() - Run on unfiltered messages before filtering step - Fix off-by-one: use max(0, ...) instead of > 0 guard
…-safe - Add `defaults: UserDefaults = .standard` parameter to ConnectionManager, ConnectionIntent, OnboardingState, DemoModeManager, and LinkPreviewPreferences - Replace all UserDefaults.standard access in production code with the injected instance - Add persistIntent() helper on ConnectionManager to route all 8 persist() calls through the injected defaults - Rewrite all 7 affected test suites to use isolated UserDefaults (UUID-scoped suite names) — eliminates setup/teardown cleanup - Remove .serialized trait from ConnectionIntentPersistenceTests and ConnectionManagerDisconnectDiagnosticsTests - Pass values directly in pill behavior tests instead of writing to UserDefaults; keep 2 integration tests using injected ConnectionManager
- replace fixed async sleeps with deterministic condition polling in flaky tests - wait for BLE auto-reconnect handler wiring before simulating events in diagnostics tests
- Support 1/2/3-byte path hashes via encoded path_len byte (mode + hop count) - Preserve direct (zero-hop) contacts on config import by distinguishing nil (flood) from empty string (direct) in the outPath branch - Validate pathHashMode <= 2 during import, throw invalidPathHashMode instead of crashing via encodePathLen's precondition - Use savedPath.hashSize when reconstructing saved trace path hop boundaries - Decode hop count from encoded path_len for CLI timeout calculation
- Support variable-width path hashes (1, 2, or 3 bytes per hop) across MeshCore, PocketMeshServices, and PocketMesh UI - The `path_len` byte now encodes hash size mode in its upper 2 bits and hop count in the lower 6 bits, backward compatible with existing 1-byte hashes (mode 0) - Add `setPathHashMode` command (0x3D) gated behind firmware v10+, with Settings picker localized in all 9 languages
Split PersistenceStore (2654 lines) into 8 files: - PersistenceStore.swift (core: type decl, stored props, init, warm-up, discovered nodes) - PersistenceStore+Devices.swift (device CRUD, activation, sync timestamp) - PersistenceStore+Contacts.swift (contact CRUD, unread counts, mention tracking, helpers) - PersistenceStore+Messages.swift (message CRUD, reactions, heard repeats, mention seen) - PersistenceStore+Channels.swift (channel CRUD, unread counts, blocked senders, mention tracking) - PersistenceStore+Rooms.swift (remote node sessions, room messages, connection state) - PersistenceStore+Diagnostics.swift (trace paths, RX log, debug log, node status snapshots) - PersistenceStore+Metadata.swift (badge counts, link previews) Access control changes: - None Cleanup: - Removed unused MeshCore import from core file (all MeshCore-dependent methods moved to extensions)
Split MessageService (1164 lines) into 3 files: - MessageService.swift (core: type decls, stored props, init, event listening, ACK handling + handler setters) - MessageService+Send.swift (direct message send, retry loop, routing change detection, channel messages, message factory helpers) - MessageService+ACK.swift (periodic ACK expiry checking, cleanup, fail-all, pending count) Access control changes: - logger: private → internal (needed by +Send and +ACK for logging) - session: private → internal (needed by +Send for send operations) - dataStore: private → internal (needed by +Send and +ACK for persistence) - config: private → internal (needed by +Send for retry configuration) - pendingAcks: private → internal (needed by +Send for trackPendingAck, +ACK for expiry/cleanup) - messageFailedHandler: private → internal (needed by +ACK for failure notification) - retryStatusHandler: private → internal (needed by +Send for retry progress) - routingChangedHandler: private → internal (needed by +Send for routing change notification) - ackCheckTask: private → internal (needed by +ACK for periodic checking lifecycle) - checkInterval: private → internal (needed by +ACK for check interval) - inFlightRetries: private → internal (needed by +Send for concurrent retry guard) Kept private (no cross-file access needed): - contactService, eventListenerTask, ackContinuations, ackConfirmationHandler Cleanup: - Removed unused Private Helpers MARK section from core (all helpers moved to +Send) - Removed unused import os from extension files (only needed in core)
Split BLEStateMachine (1727 lines) into 2 files: - BLEStateMachine.swift (core: type decl, stored props, init, public API, callback handlers, state transitions) - BLEStateMachine+CBDelegate.swift (BLEDelegateHandler class: CB delegate bridge, data continuation, write sequence correlation) Deferred sections (kept in core): - Callback handlers (+Connection): accesses 30+ private stored properties and 10 private methods from core; far exceeds the 5-member widening threshold - State transitions (+Transitions): accesses 16 private stored properties and 1 private method from core; far exceeds the 5-member widening threshold Access control changes: None BLEDelegateHandler is a separate class (not an extension of the actor) that only references BLEStateMachine through internal-access methods, so zero visibility widening was needed. Cleanup: - Removed unused `import os` from core file (only used by delegate handler for OSAllocatedUnfairLock)
Split ChatViewModel (2173 lines) into 5 files: - ChatViewModel.swift (320 lines, core: type decl, stored props, init, conversation cache, pagination state, timestamp helpers, environment key) - ChatViewModel+Messages.swift (1080 lines, notification level, favorites, conversation list, message loading/sending, pagination, message previews, message actions, display items, message queue) - ChatViewModel+Channels.swift (284 lines, channel message loading/sending, retry, channel sender tracking) - ChatViewModel+Previews.swift (275 lines, link preview state management, inline image state management) - ChatViewModel+Reactions.swift (246 lines, reaction sending, reaction filtering, reaction summary updates) Access control changes: - logger: private let → let (needed by all extensions) - channelSenderNames, contactNameSet: private var → var (needed by +Channels, +Messages) - displayItemIndexByID, lastMessageCache, sendQueue: private var → var (needed by +Messages) - previewStates, loadedPreviews, previewFetchTasks: private var → var (needed by +Previews, +Messages) - loadedImageData, decodedImages, imageIsGIF, imageFetchTasks: private var → var (needed by +Previews) - inFlightReactions: private var → var (needed by +Reactions) - pageSize: private let → let (needed by +Messages, +Channels) - totalFetchedCount, dividerComputed: private var → var (needed by +Messages, +Channels) - dataStore, linkPreviewCache, messageService, notificationService: private var → var (needed by multiple extensions) - channelService, contactService, syncCoordinator: private var → var (needed by +Messages) - appState: private weak var → weak var (needed by multiple extensions) - displayItems, messagesByID: private(set) var → var (written by +Messages, +Previews) - isLoadingOlder, hasMoreMessages, newMessagesDividerMessageID: private(set) var → var (written by +Messages, +Channels) - isProcessingQueue: private(set) var → var (written by +Messages) - QueuedMessage: private struct → struct (needed by +Messages for sendQueue access) - computeDividerPosition: private func → func (needed by +Messages, +Channels) - rebuildDisplayItem: private func → func (moved to +Previews, needed by +Reactions) - clearPreviewState: private func → func (moved to +Previews, needed by +Messages, +Channels) - cleanupPreviewState: private func → func (moved to +Previews, needed by +Messages) - filterOutgoingReactionMessages: private func → func (moved to +Reactions, needed by +Messages, +Channels) - addChannelSenderIfNew: private func → func (moved to +Channels, needed by +Messages) Cleanup: - Added Foundation import to +Channels and +Reactions (UUID type resolution)
Split SyncCoordinator (1362 lines) into 3 files: - SyncCoordinator.swift (core: type decl, stored props, init, state setters, sync activity tracking, notifications, blocked contacts cache, timestamp correction) - SyncCoordinator+Sync.swift (performFullSync, performResync, onConnectionEstablished, onDisconnected, sync helpers) - SyncCoordinator+MessageHandlers.swift (wireMessageHandlers as a unit, wireDiscoveryHandlers, persistReactionIfNew, message handler helpers) Access control changes: - logger: private → internal (needed by +Sync, +MessageHandlers) - deduplicationCache: private → internal (needed by +Sync, +MessageHandlers) - hasEndedSyncActivity: private → internal (needed by +Sync) - onSyncActivityStarted: private → internal (needed by +Sync) - unresolvedChannelIndices: private → internal (needed by +Sync, +MessageHandlers) - lastUnresolvedChannelSummaryAt: private → internal (needed by +Sync, +MessageHandlers) - unresolvedChannelSummaryIntervalSeconds: private → internal (needed by +MessageHandlers) - reactionTimestampWindowSeconds: private → internal (needed by +MessageHandlers) - onDirectMessageReceived: private → internal (needed by +MessageHandlers) - onChannelMessageReceived: private → internal (needed by +MessageHandlers) - onRoomMessageReceived: private → internal (needed by +MessageHandlers) - onReactionReceived: private → internal (needed by +MessageHandlers) - setState(): private → internal (needed by +Sync) - setLastSyncDate(): private → internal (needed by +Sync) - endSyncActivityOnce(): private → internal (needed by +Sync) - startSuppressionWatchdog(): private → internal (needed by +Sync) - cancelSuppressionWatchdog(): private → internal (needed by +Sync) - wireMessageHandlers(): private → internal (moved to +MessageHandlers, called from +Sync) - wireDiscoveryHandlers(): private → internal (moved to +MessageHandlers, called from +Sync) Cleanup: - Removed unused `import OSLog` from core (PersistentLogger is a project type)
Split ConnectionManager (3021 lines) into 6 files: - ConnectionManager.swift (core: type decls, stored props, init, circuit breaker, persistence helpers, createDevice, session/sync helpers, cleanup, state invariants) - ConnectionManager+BLE.swift (BLE scanning, diagnostics, health checks, reconnection watchdog, connectWithRetry, performConnection, connection loss handling) - ConnectionManager+WiFi.swift (WiFi heartbeat, disconnection handling, reconnection, connectViaWiFi, WiFi health checks) - ConnectionManager+Lifecycle.swift (activate, app lifecycle, connect/disconnect, switchDevice, simulatorConnect, sync health) - ConnectionManager+Pairing.swift (pairing, forget device, node management, device updates, accessory management, AccessorySetupKitServiceDelegate) - ConnectionManager+Session.swift (BLEReconnectionDelegate: session teardown/rebuild, transport helpers, reconnection failure handling) Access control changes: - connectionState: public private(set) → public internal(set) - connectedDevice: public private(set) → public internal(set) - services: public private(set) → public internal(set) - currentTransportType: public private(set) → public internal(set) - connectionIntent: private(set) → internal (removed private(set)) - logger, modelContainer, defaults, transport, stateMachine, accessorySetupKit, reconnectionCoordinator, simulatorMode: private let → let (internal) - wifiTransport, session, bleScanTask, bleScanRequestID, wifiReconnectTask, wifiReconnectAttempt, wifiHeartbeatTask, isHandlingWiFiDisconnection, lastWiFiReconnectStartTime, reconnectionWatchdogTask, sessionsAwaitingReauth, resyncTask: private var → var (internal) - wifiMaxReconnectDuration, wifiReconnectCooldown, wifiHeartbeatInterval: private static → static (internal) - shouldAllowConnection, recordConnectionFailure, recordConnectionSuccess, persistConnection, clearPersistedConnection, persistDisconnectDiagnostic, persistIntent, createDevice, initializeSession, syncDeviceTimeIfNeeded, performInitialSync, startResyncLoop, cancelWiFiReconnection, cancelResyncLoop, cleanupResources, cleanupConnection, configureBLEPacing: private → internal (needed by extension files) - Moved private methods become internal by default in extension files: startAdoptingLastSystemConnectedPeripheralIfAvailable, handleBluetoothStateChange, handleConnectionLoss, startReconnectionWatchdog, stopReconnectionWatchdog, connectWithRetry, performConnection, logDeviceNotFoundDiagnostics, isDeviceNotFoundError, handleWiFiDisconnection, startWiFiReconnection, reconnectWiFi, completeWiFiReconnection, startWiFiHeartbeat, stopWiFiHeartbeat, connectAfterPairing, removeContacts Cleanup: - None
- Make saveRemoteNodeSession(_:) -> RemoteNodeSession private since it returns a SwiftData model entity and is only called internally by saveRemoteNodeSessionDTO
- Revert channelService from var to private var — only accessed in configure() methods within the core file, no extension file uses it
…ager members - defaults property: only accessed from core methods, never from extensions - WiFi-internal methods (startWiFiHeartbeat, handleWiFiDisconnection, startWiFiReconnection, reconnectWiFi, completeWiFiReconnection): only called within ConnectionManager+WiFi.swift - Pairing-internal methods (connectAfterPairing, removeContacts): only called within ConnectionManager+Pairing.swift
Extend MockMessagePollingService with handler setter capture methods, add MockAdvertisementService for discovery handler testing, and create ContactDTO+Testing and MessageDTO+Testing factory helpers.
Add ~55 tests across 5 new test files covering previously untested code in the monolithic file split: - MessageServiceSendTests: validation, pending messages, retry guards - MessageServiceACKTests: expiry checking, cleanup, fail-all lifecycle - SyncCoordinatorMessageHandlerTests: parseChannelMessage, blocked sender cache, handler wiring smoke tests, notification version counters - ConnectionManagerPairingTests: state guards, device updates, pre-repeat settings, data operations - ConnectionManagerSessionTests: state management, reconnection delegate, WiFi health check early returns Widen parseChannelMessage from private to internal for testability.
- Add availableRooms property and availableNodes computed union in TracePathViewModel, PathManagementViewModel - Update bestRepeaterMatch and map resolution to search rooms via availableNodes - Add "Include Rooms" toggle in TracePathListView with per-row Room badge - Add onChange trigger for availableRooms in TracePathMapView for initial centering - Add includeRooms localization key in all 9 languages and regenerate L10n.swift - Add createTestRoom helper and 4 new room tests
- Added DiscoveredNodeDTO fallback to RepeaterResolver so paths with unknown hashes can resolve names from the discovery list - Updated MessagePathViewModel, RepeatDetailsContent, RepeatRowView, and MessageActionsSheet to pass discovered nodes into the resolver - Added unit tests for the fallback resolution behavior
- Replace green antenna icon with numeric packet rate when connected - Show 0 when the activity is stale - Fall back to orange antenna icon when disconnected
- Add channelSenderOrder map (name → timestamp) to ChatViewModel, built alongside channelSenders on each message load - Add senderOrder parameter to MentionUtilities.filterContacts; recent senders sort first, rest fall back to alphabetical - Snapshot sender order when @ is typed in ChannelChatView so the list stays stable while new messages arrive - Add two tests covering full and partial sender order sorting
- Add Contact Info expandable section for editing owner.info - Character count overlay inside text field (119 char limit) - Add CLIResponse.ownerInfo case with pipe-to-newline conversion - Add owner info tests to CLIResponseTests - Add localized strings across all 9 supported languages
- Hide separator above error banner row - Remove list row insets so "Failed to load" and "Try Again" are centered
- Add ChatFilterPicker view modeled after NodeSegmentPicker - Add .all case to ChatFilter, remove .favorites and systemImage - Show picker at top of conversation list in both stack and split layouts - Empty state renders picker in a List with ContentUnavailableView overlay - Remove ChatsFilterMenu and searchScopes; search now ignores active filter - Remove dead favorites/accessibility filter strings from all 9 locales
- Batch all conversation state mutations in loadAllConversations into a single synchronous block so SwiftUI sees one consistent state - Move .id() from split view to detail pane so only the detail resets on selection change - Remove redundant onRefreshConversations, consolidate into onLoadConversations - Guard onChange handlers to skip reloads while a deletion is in progress - Reload conversations after each deletion to reflect updated state
- Add MeshCoreError+LocalizedError with @retroactive conformance and device error code table - Add LocalizedError extensions on ProtocolError and 8 service error enums - Remove "Session error:" prefix from SettingsServiceError and RemoteNodeError - Fix ChannelService.classifyError to use meshError.localizedDescription - Add ErrorLocalizationTests with 19 tests covering all error types
- Add nodeType field to NodeDiscoveryResult from DiscoverResponse - Track added/adding state in view model; populate addedPublicKeys from contacts already fetched during name resolution - Wire Add/Added button to repeater rows only (nil onAdd for sensors) - Reuse ContactService.addOrUpdateContact with flood-routing defaults - Add haptic feedback for add success and error
Avatar initials now prefer an emoji if the name contains one, and show only one character for single-word names instead of two. Moves the Character.isEmoji extension from ReactionParser into its own file.
- Add `notificationService` parameter to `performFullSync` (default nil) - Clear suppression right after `pollAllMessages()` returns, not after the handler drain wait — closes the window where genuine new messages lose their notifications during post-sync operations - Reorder `drainHandlersAndResumeNotifications` to clear suppression before the 30s drain wait as defense-in-depth for error paths - Pass `notificationService` at both call sites (onConnectionEstablished, performResync) - Add test verifying suppression is cleared after poll completes
- Added name: String init to ContactAvatar alongside existing contact: init - Deleted duplicate DiscoveredNodeAvatar view - Updated DiscoveryView to use ContactAvatar directly
- Guard "Connected" label in RoomConversationRow behind connectionState == .ready so BLE disconnect immediately updates all room rows, bypassing SwiftData timing issues - Reset all room session connections in activate() on app launch so sessions don't persist as "connected" across restarts
- Add MalwareDomainFilter using URLhaus blocklist for known malware domains - Add URLSafetyChecker to gate URL fetches in services and previews - Block unsafe URLs in InlineImageCache and LinkPreviewService before fetch - Show MalwareWarningCard in chat bubbles for flagged links - Add malwareWarning preview state and localized warning strings - Add URLhaus filter acknowledgement to Settings bundle
- Replace nil with .all to match updated filtering API
- Migrate DataExtensionsTests (15 pure function tests) - Migrate ChatViewModelQueueTests (async setUp restructured to static helper)
…llState - Move displayText, systemImageName, isFailure, textColor to StatusPillState - Tests now exercise production code instead of reimplementing display logic
- Replace Task.sleep with waitUntil in LineOfSightViewModelTests, CLIToolViewModelTests, ProtocolBugFixTests - Inject explicit timestamps in NodeSnapshotServiceTests via PersistenceStore overload - Remove duplicate tests from SyncCoordinatorMessageHandlerTests - Fix misleading test name in BLEPhaseTests - Add .serialized trait to DemoModeManagerTests.testSingletonPattern - Convert for loops to @test(arguments:) in ImageURLDetectorTests, OCVPresetTests, ErrorLocalizationTests, HashtagUtilitiesTests
- Force .large detent on iPad so the sheet opens at full height - Extract performAction helper to deduplicate dismiss-wrapping closures - Deduplicate ActionsEmojiSection instantiation via emojiSection property - Pass availability from parent instead of recomputing in 4 sub-views - Remove unused OSLog import
- Use ViewThatFits to center buttons when they fit, scroll when they overflow - Extract EmojiButtonRow struct to avoid duplicating button content
- Replace wrapper closures with direct function references
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.