Conversation
Add a Chile (CL) community radio preset so devices in Chile get a recommended configuration: 927.875 MHz, 62.5 kHz bandwidth, SF 8, CR 5. - Add a `southAmerica` case to `RadioRegion` (short code "SA") so Chile is grouped under its own "South America" section in the preset picker instead of being shoehorned into another continent. - Register the `cl` preset in `RadioPresets.all`, available to country CL. - Map CL → .southAmerica in `RegionalAreas.continents` and add Chile to the selectable countries catalog so it resolves via the country tier of `recommended(for:)`. - Prefer South America first in `regionsForLocale` for a CL locale.
feat(radio): add Chile region radio preset
…rom radio When a contact is in the app's database but missing from the radio's firmware contact table (backup restore, radio reset), CMD_SEND_LOGIN comes back ERR_CODE_NOT_FOUND (0x02), which was showing as "Item not found on device." A blank password is not the cause; firmware accepts empty passwords without complaint. The fix catches notFound during login, pushes the local contact to the radio with the flood-route sentinel via addContact (CMD 0x09), syncs the local row so keep-alive routing stays consistent, then retries. A full contact table now returns a specific error with an actionable message rather than the generic device error. - Widen RemoteNodeService.session to include ContactSessionOps so login can call session.addContact (zero call-site changes; MeshCoreSession already conforms via MeshCoreSessionProtocol) - Add sendLoginHealingIfNeeded and addLocalContactToRadio to RemoteNodeService+Login.swift; preserve RemoteNodeError through the catch - Add ContactDTO.floodedContactFrame(asOf:); refactor ContactService.resetPath to use it - Add RemoteNodeError.radioContactsFull, errorDescription, userFacingMessage, and the L10n key across all 9 locales - Extend MockMeshCoreSession with RemoteAccessSessionOps and a scriptable sendLogin stub - Add RemoteNodeLoginHealTests: heal+retry, table-full, no-local-contact
- "View on Map" opens a full-screen map with the repeater centered and each located neighbor pinned by an SNR-colored link line - Map controls toolbar (north-lock, location, layers, labels) plus center-all that fits the repeater and its neighbors - Auto-zoom on appear, framed inside the chrome, re-issued once the map style finishes loading - Neighbors without a usable location surface in a top pill that pushes their list - NeighborSNRMapBuilder with tests computes the points, links, and fit region - Add RemoteNodeSessionDTO location helpers and shared SNR line/badge styling - L10n strings across all 9 locales
- pulled the pill into a shared PathDistanceBanner so trace map and message path map use the same component instead of duplicating TracePathResultsBanner - added totalDistance() to sum great-circle distance over a coordinate chain - message path map pill shows total hop count plus the drawn path's distance
- decimalPad has no minus key, so switch lat/lon to .numbersAndPunctuation - pin lat/lon FormatStyle to .locale(.posix) so a comma from that keyboard doesn't get read as the decimal separator
- reject out-of-range coordinates and over-length names instead of letting firmware silently normalize them - validate all three fields up front so a bad one can't leave a partial apply - add inline per-field errors and validation strings for all locales
allSatisfy(\.isFavorite) doesn't compile under Swift Testing's macro expansion. Switched to a closure so the test target builds.
fix: cli toolbar padding
The firmware already sends a hop-aware airtime estimate in suggested_timeout_ms, but the app rejected any value outside [5s, 60s] and fell back to a flat 30s. On the default SF10/BW250 preset the radio estimates ~3.2s for a zero-hop trace, so every ping sat for 30s before reporting no response. - Clamp the scaled hint into a per-use-case band instead of rejecting it; only fall back to a default when the hint is absent (0) - Add zero-hop and flood profiles with distinct floor/default/ceiling so a single-neighbor ping and a mesh-wide flood no longer share bounds - Point ping at the zero-hop profile; keep path discovery and multi-hop traces on the wider flood band - Pin the SF10 default-preset case in tests so it can't fall back to 30s
- Persist six per-type counters (sent/received direct and flood, direct/flood duplicates) on NodeStatusSnapshot and its DTO - Overlay Direct and Flood lines in the Packets Sent/Received charts, replacing the standalone totals, and add a Duplicates chart - Generalize MetricChartView to multi-series with a per-series scrub readout; keep receiveErrors single-series - Add RemoteNodes chart-title strings across all locales - Cover snapshot round-trip, legacy decode, and multi-series ordering in tests
- Track every neighbor prefix seen across stored snapshots, not just the previous one, so a neighbor that went silent and came back isn't flagged new - Fetch the previous snapshot and the seen-prefix set together in one neighborBaseline call, so a render can't pair a fresh baseline with a stale set - Cover the silent-then-returning case and in-window exclusion in tests
Image-extension URLs that serve an HTML landing page (imgur, pasteboard) never rendered. The extension classifier routed them to the inline-image path, which can't decode HTML, so the card stayed blank. Detect text/html at fetch time and reroute to the link-preview fragment, which pulls the page's og:image. - InlineImageCache returns .notImage for text/html without touching the negative cache, so the URL stays retryable - MessageBuildInputs carries a precomputed isInlineImageURL; the builder routes from that field instead of reclassifying - ChatViewModel tracks page-serving URLs per conversation and re-fires the preview fetch on reroute, including in-flight dedup twins that would otherwise shimmer forever
- Add hasFavorites computed property to ContactsViewModel - On first load, land on Favorites instead of Contacts when there's at least one favorite; leave it alone if the user already picked a segment - Add tests for hasFavorites
- Register the meshcore scheme alongside meshcoreone so iOS opens MC1 when the Camera scans a QR or another app invokes the scheme. Routed URLs reuse the existing contact, channel, map, and hashtag handlers and confirmation sheets. - Stage cold-launch URLs in PendingExternalURL and route only after AppState init settles. Without this, the before-first-unlock AppState swap and the failed-reconnect teardown each wipe a staged link before its sheet can present. - Treat a malformed meshcore URL as unhandled and restore the previous tab, instead of stranding the user on an empty Chats tab. - Show the public key above the free-text name in AddContactConfirmationSheet. A planted QR makes name-over-key phishing likelier than the in-chat link path.
- Mark the RelativeTimestampText and BubbleStatusRow suites @mainactor for their main-actor-isolated static calls - Drop redundant #require on statically-valid literal URLs in the MessageFragmentBuilder tests - Remove no-op await/async on the synchronous InlineImageCache stores - Change never-mutated LinkPreviewPreferences locals to let - Swap deprecated UITraitCollection(traitsFrom:) for modifyingTraits - Move .serialized up to the DemoModeManager suite so it guards the shared singleton, and exercise both view models in the NodeCLI isolation test - Drop the always-true UIColor nil check in the bridging test
- Back the appState environment default with a single AppState.placeholder instead of minting a fresh AppState() on every default resolution, each of which leaked a self-retaining StoreService transaction listener - Add an isPlaceholder init flag so the stand-in skips publishing the process-global DebugLogBuffer and skips the onboarding path-suggestion task - Reuse PersistenceStore.schema for the in-memory container so DebugLogEntry resolves; the drifted preview schema had omitted it - Cover shared identity, inertness, and the debug-buffer skip path with tests
- Match incoming channel sender names against saved contacts; show the nickname with the raw sender name in parentheses - Add SenderNameLabel with an unverified-identity indicator. A channel sender isn't cryptographically verified, so a name match could be someone else - Only match a name owned by exactly one contact, so an ambiguous collision never asserts a nickname - Carry the nickname into the DM picker sheet title and the actions preview header - Include the nickname and unverified cue in the VoiceOver label - Trim surrounding whitespace when storing a contact nickname
- Set repeat-869 to 869.495 MHz to match the firmware's allowed repeat set; 869.000 was rejected by isValidClientRepeatFreq - Enabling Repeat Mode now only changes the frequency and leaves bandwidth, spreading factor, and coding rate as they were - Match repeat presets on frequency alone so the picker and settings row still resolve once BW/SF/CR are kept - Drive the Repeat Mode toggle off a synchronous flag so a programmatic flip no longer loops the confirmation or desyncs the picker - In Advanced Settings, snap to the nearest allowed repeat frequency when enabling and restore the previous frequency when disabling, so the toggle never submits a value the firmware rejects and never strands the radio on the repeat frequency - Stop the Advanced radio form reloading mid-apply, which flickered the frequency field as the frequency and clientRepeat updates arrived as separate events - Correct the Advanced repeat footer to list 869.495 MHz across all locales - Share one nearestRepeatPreset helper between the preset and Advanced screens - Add RepeatPresetTests for the firmware frequency contract, frequency-only matching, and nearest-frequency snapping
- Fall back to a plain-GET og:image/twitter:image scrape in LinkPreviewService when LinkPresentation returns no hero image - Send scrape and image GETs through a session that re-checks every redirect hop, so a 302 to a private host is refused - Cap the HTML and image downloads with streaming byte limits and a pixel-bounded ImageIO decode - Widen the fetch semaphore doc to note it now covers these GETs - Test the meta-tag parser and the network guards
- PersistentLogger.persist goes through DebugLogBuffer.record: entries queue (max 500, oldest dropped) while shared is nil and drain in order once a buffer is assigned - entries lost to save failures or the requeue cap are counted and reported as a summary entry on the next successful save - tests cover the pending queue, drain order, overflow, and drop summary
- the auto-reconnect rebuild now sets .connected up front like a fresh connect so the syncing pill shows, and its session.start and queryDevice get the same 10s timeout as the initial connect - stop the old session during reconnect teardown (keeping the transport for the pending reconnect) so its receive and auto-fetch loops end now instead of parking on the finished dispatcher stream until dealloc; the poll loop checks for cancellation each iteration so it actually unwinds - the resync loop guards on container identity at the loop top like the success path already did, so a loop orphaned by a later reconnect cycle can't burn its attempts against a dead container and then disconnect the healthy successor - promote to .ready before the post-sync room re-auth, matching promoteToReady's ordering, so the syncing pill and the send queue's ready gate don't wait on work that isn't sync - tests for the rebuild window, bond loss mid-reconnect, and the superseded and cancellation paths
- map bond failures (CBError.peerRemovedPairingInformation, CBATT auth codes, and ATT auth codes on a write) to authenticationFailed at every disconnect and write callback, latched per failure episode so watchdog retries don't re-alert; the latch clears on ready promotion and explicit disconnect - a peripheral still connected after the discovery extension budget is spent, or a disconnect in the discoveryComplete window, is classified as a dead bond and routed to guided re-pair instead of retrying timeouts forever - removeFailedPairing demotes the Device row to a ghost instead of deleting it, so a radio that lost its bond re-pairs onto its original radioID and keeps its contacts, messages, and channels; a stranded association is swept only when it maps to no saved device, leaving the live connection, active attempt, and saved radios protected - manual Connect on a dead-bond radio and a fresh pair rejected on the PIN each present guided recovery, with copy distinct to each case; presentConnectionFailure clears stale pairing state between alerts - rebuild the ASK session after ASAccessorySession invalidation, so pairing isn't wedged into a silent no-op until app relaunch - AppState presents the recovery flow; new copy is localized - tests for the latch, routing, each mapped callback site, the re-pair round trip, and the sweep and cancellation paths
- a connect attempt parked in .waitingForBluetooth is failed before state restoration claims the machine; transition(to:) never resumes a phase continuation, so clobbering the phase would hang that connect and leak its device claim, turning every later Reconnect into a no-op - an isReconnecting disconnect arriving in .idle or .waitingForBluetooth (no operation owns a peripheral) is treated as a full disconnect instead of entering .autoReconnecting to wait on a reconnection this machine never started - the RSSI keepalive is cancelled during stop's direct-write path, which bypasses cleanupPhaseResources, and the connection-timeout task no-ops when cancelled, so neither outlives shutdown - auto-reconnect entry stands down for a different in-flight manual connect and adopts a matching one, rather than disconnecting the transport and cancelling the OS pending connect, which would strand a reconnect-cycle claim that silently disables the watchdog, the foreground health check, and power-on recovery - tests for restoration, teardown, the auto-reconnect entry race, and the circuit-breaker cooldown boundary
- Collapse the standalone Inline Images setting into the Link Content master toggle; DM/channel scope and GIF autoplay nest beneath it - Add InlineImage .disabled tap-to-load state so images sit behind a placeholder when the master is on but the scope's auto-resolve is off - Gate inline images on previewsEnabled in MessageFragmentBuilder; drop the now-unused showInlineImages EnvInputs field and default constant - Render the disabled placeholder as a text-style bubble to skip the edge-to-edge image clip pass - Persist the image-URL-serves-a-page verdict in InlineImageCache so a reloaded card is not re-classified as an inline-image shimmer when the same chat is reopened - Rename Link Previews to Link Content; add the reduce-motion note and update the privacy footer copy across all locales
- Add onAutoReconnectStarted, fired from BLEReconnectionCoordinator after the reconnect cycle is claimed and before session teardown, so the Live Activity write lands at the front of the disconnect wake window rather than behind teardown - Gate entry on connectionIntent.wantsConnection so a user disconnect or a stand-down for a different device doesn't flash a false loss - Self-heal a stale activity cached disconnected while the same radio is actually connected, keyed on a radio-scoped provider so a connection to a different radio can't relabel it - Cover entry ordering and gating in BLEReconnectionCoordinatorTests and the three entry paths in ConnectionManagerAutoReconnectEntryTests
- Read the dBm value out of a `get tx` reply instead of requiring the whole string to be an integer, so ZephCore's "22dBm (apc=off)" no longer fails to load and blocks the radio settings apply - With Adaptive Power Control on, take the `max=` ceiling that `set tx` writes back rather than the reduced live power, so applying doesn't quietly lower the configured ceiling - Cover the apc=off and apc=on reply shapes with tests
- Compute one Y-axis domain across every packet series and apply it to the Sent, Received, Duplicates, and Errors charts - The Direct/Flood split had given each chart its own scale, so counter growth rates could no longer be compared at a glance
- Apply .listSectionSpacing(.compact) to the telemetry, telemetry overview, and node status history lists - Shrinks the blank space around the time range selector so it matches the other node status screens
- re-issue the pending connect on a transient didFailToConnect instead of going idle. The watchdog can't run while the app is suspended, so giving up meant no background reconnect at all - cap retries at maxAutoReconnectConnectFailures. Definitive bond errors still tear down immediately; if the budget runs out on mostly encryption timeouts, treat it as a bond failure so re-pair guidance still appears - a lone CBError.encryptionTimedOut now maps to connectionFailed, not authenticationFailed. One timeout is a weak link, not a lost bond - show the pairing-failure alert only while the app is active, clear the surfacing latch on foreground, drop stale alert state once connected
- Add a success-rate percentage to the batch complete and success labels - Default batch size to 3 and drop the size-10 chip - Compute the percentage from success count over batch size
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.