Skip to content

v1.3.0 - #377

Merged
Avi0n merged 69 commits into
mainfrom
dev
Jul 24, 2026
Merged

v1.3.0#377
Avi0n merged 69 commits into
mainfrom
dev

Conversation

@Avi0n

@Avi0n Avi0n commented Jul 24, 2026

Copy link
Copy Markdown
Owner

No description provided.

Avi0n and others added 30 commits July 13, 2026 08:27
- Route on-device commands over the companion protocol: clock/clock sync,
  ver, board, advert/floodadv, reboot, and typed get/set
- Fall bare "set <key> <value>" and "get <key>" through to the companion
  custom-var frames when the key is not a typed setting, with "get custom"
  to dump all vars and a leading underscore escape hatch for name collisions
- Add getTime/setTime session ops and handle device errors from custom-var frames
- Gate destructive commands like reboot behind a yes/no confirmation
- Extend tab-completion with typed keys plus live custom-var names on bare
  get/set, prefetched once per connection
- Localize across 9 locales; cover parser, executor, and completion with tests
ZephCore exposes a virtual "V-contact" that streams through GET_CONTACTS
but occupies no real contact-table slot. MC1 treated it like an ordinary
contact, so its own cleanup paths acted on it: the full-sync prune and the
overwrite-oldest (0x8F) handler deleted the V row and its messages, a
`set v.contact off` push spuriously cleared the storage-full flag, config
import counted its phantom slot against capacity, and the remove/delete
paths could CMD_REMOVE it and turn the firmware feature off.

- Add VContactIdentity to derive the ZephCore loopback admin contact key
- Skip the full-sync prune and the overwrite-oldest storage-full clear for
  the V-contact
- Exclude the V-key from config-import capacity accounting
- Block remove/removeLocal/bulk-remove and hide the remove/delete UI for
  the V-contact
- Drop the stale V-contact after an identity rotation
refactor: chat page

Rework the chat view for a full-height, edge-to-edge layout on iOS 26
and clean up the overall styling.

- make the chat window take full height / edge-to-edge on iOS 26
- restyle the compose bar and icons; send button pops in when a message is ready to send (Signal-style)
- add a liquid-glass header bubble for the chat name
- unify chat bubbles with in-bubble state/footer and a send-count footer
- add date dividers and a new-messages divider; open chats scrolled to it
- migrate to swiftui-messaging-ui and pin MessagingUI to an exact revision
- add cache prewarming and build link previews synchronously to avoid shimmer/layout flashes on first view
- improve chat scrolling, keyboard dismiss, and gesture handling
- Chats could open above the divider or jump to the bottom right after opening
- The library now anchors the divider until the user scrolls
- The first geometry report consumes the divider target instead of arming append-follow
- Retire the target after use so a theme rebuild doesn't re-jump to it
- Pin MessagingUI to the fork revision with the anchor window
- Read the firmware version from the owner-info response. Nodes
  predating owner-info return an empty string, which maps to nil so
  the row stays hidden rather than showing a blank value
- Keep the firmware row out of the owner-text branch: firmware comes
  back even when owner_info is empty
- Add a footer to the section, matching the other telemetry sections
- Test the firmware capture and the empty-string case
- The prime paths in AppState.prefetchConversation and
  ChatPrewarmRefresher built a whole ChatViewModel just to throw it
  away. They now use ChatTimelinePrimer, so the .prime writer role has
  one caller.
- Move per-message bake state (preview/image caches, New Messages
  divider) to ChatMessageBakeState, which is not Observable: redraw is
  decided by the Equatable MessageItem.
- primeInitialMessages and primeInitialChannelMessages ran the same
  fetch, divider, filter, write and bake sequence once per conversation
  kind. Collapse that core into ChatTimelinePopulator, which the primer
  and both prime methods call; the channel path keeps its own extras.
- Sender resolution takes a ChatSenderTables snapshot; the observed
  mention-picker tables stay on the view model.
- ChatViewModel.configure drops its role parameter and the writerRole
  property; bindCoordinator always binds .interactive.
- The primer installs rebakeRow as its renderItemRebuilder, so a reload
  draining while it owns the writer rebakes the rows it refreshes.
- ChatTimelinePrimerTests cover the open-races-prime case, channel
  sender names, a prime denied while open, and a reload draining
  mid-prime.
recoverOrphanedLoadingState reset previewStates to .idle without
rebuilding the row's item. A caller that recovers and then returns
without fetching leaves an item built from .loading, which carries no
fetch-task id, so the cell never re-arms and the row shimmers with no
fetch behind it. Reset and rebake together, matching the sibling
resetLoadingStateAfterCancellation.
The channel type repeated what the title and avatar already show. The
region is the fact worth the space: it decides who actually receives a
flood send.

- Show "Region: <name>" as the second line, with "(default)" when the
  region comes from the radio's default
- Hide the line entirely when no region resolves
- Center the title and second line in the shared navigation header
- Drop the subtitle accessibility label, now that there's no separator
  for VoiceOver to misread
Add NodeLocationFix.primaryFix(from:) to extract the first valid GPS
LPP data point, dropping (0,0) null-island and out-of-range fixes via
a new CLLocationCoordinate2D.isValidFix helper. Add latitude/longitude
columns to NodeStatusSnapshot and its DTO, both nil unless a valid fix
was captured.
- Capture the node's primary GPS position on status snapshots and expose the live location
- Thread that location through the snapshot capture path and node status route
- Add NodeLocationMapView plus a location history section with a path-preview thumbnail
- Draw the plotted path polyline and pins in the map snapshot, framed to their bounds
- Build node-location paths through LocationPathMapBuilder with camera-bounds fitting
- Add RemoteNodes location map localization keys across all nine locales
- Cover location capture and path building with unit tests
- Capture and persist GPS altitude with each node fix, dropping values outside a plausible range
- Add a report list under the History map: one row per fix with recency, timestamp, coordinates, and altitude
- Show a callout when a report pin on the full-screen map is tapped
- Tapping a report row opens the map with that pin already selected and its callout up
- Tighten the initial and center-all camera so the path fills the screen instead of sitting in the middle
- Seed a sample GPS track in the simulator so the History screens have content
- Nav bar never observes the timeline's collection view, so it stayed on
  its default transparent scroll-edge appearance and the title read over
  passing messages. Pin it opaque, using the theme canvas.
- Compose field takes an outline instead of a grey fill. Grey on an opaque
  bar is near-invisible in light mode and clashes with themed canvases.
- glassTitleCapsule is now contentScrollsUnderBar; it picks between
  treatments rather than requesting a capsule.
- iOS 26 untouched.
- Sender name now sits 3pt above its bubble instead of 5pt
- Room bubbles match the main chat: they had a 4pt gap and indented
  the name to the bubble's text inset instead of its leading edge
- Both bubbles place the name through one senderNamePlacement helper in
  SenderNameMetrics, so the geometry cannot drift between them
- a warm coordinator kept the previous session's divider, and a reopen
  fed it to the list as the one-shot scroll target before the fresh
  page landed
- withhold the timeline until this session's divider resolves in the
  on-screen items; present at the bottom when there's no backlog or
  the load settles without one
- delete ChatRenderState.newMessagesDividerItemID so the stale read
  is unwritable
- test the warm-reopen composition through the real bake path
- position the initial scroll target with real input-bar chrome insets
- SwiftUI can keep a popped destination's @State alive, so the closed
  chat's view model never deallocated and its interactive bind kept
  denying every arrival-time prime refresh
- performCleanup now vacates the slot via releaseWriter(owner:); the
  load task rebinds on the next appearance
- release is identity-checked so a stale view's teardown can't evict
  a successor that already bound
- the pushed DTO's count can go stale during the push transition,
  misplacing the New Messages divider even on a cold open
- populate re-fetches the count from the store (it also sizes the
  first page) and falls back to the DTO if the lookup misses
- new ChatTimelineFreshnessTests: populate freshness, the production
  prewarm hook chain, and writer release on close
…bly module

- ChatTimeline owns the bake state, the writer capability, and the
  populate/rebake entry points; ChatViewModel and ChatTimelinePrimer
  drive one implementation, role-parameterized. Rebake hooks install
  atomically with bind and capture the timeline weakly, replacing the
  duplicated rebuild closures in the view model and the primer
- loadOlder() owns the paging sequence: spinner bracket, offset math,
  reaction filter, dedupe against admitted rows, prepend + cross-page
  reorder, and the spinner-before-rebake ordering; the view model
  wrapper keeps sender registration, reaction indexing, and the error
  banner
- admit(_:) owns dedupe against the loaded window and the same-frame
  append of message plus baked render item; appendMessageIfNew keeps
  only the channel-sender bookkeeping
- stageOpen captures the open's unread count and resets the per-open
  latches and divider, so the anchor can only come from this session's
  bake; firstSnapshot/consumeAnchor own the withhold-vs-present
  decision and the one-shot latch, and the conversation view drops its
  anchor @State triad
- apply(_:) pairs every bake mutation with the rebake of its affected
  rows in one call, so a fetch result can never land without its
  redraw; clearBakeState/removeBakeState own conversation-switch and
  message-deletion cleanup
- applyStatusUpdate, enqueueReload, removeMessage, and updateMessage
  replace raw writer calls across the view model extensions; the
  writer stays reachable only for the shared reaction-indexing core
  and test seams
- timeline.conversation is assigned by stageOpen/open and directly on
  DTO refresh, replacing the currentContact/currentChannel didSet
  mirrors; a channel refresh now updates currentChannel and the
  timeline alongside the contact path
- ChatTimelineTests exercise open/loadOlder through the module surface
- deleteContact now removes the contact's messages, reactions, repeats, and pending sends in the same transactional save
- the cascade keys on the contact ID value, so orphaned data is cleaned up even when the row is already gone
- drop the paired deleteMessagesForContact call at all five delete sites
- mirror the cascade in MockPersistenceStore and add real-store cascade tests
- Move the transient-vs-escalate classification out of BLEStateMachine
  into a value-typed ReconnectPolicy: failure tallies, encryption-timeout
  majority, discovery-extension budget, error mapping, and bond grace
- Give each question its own resolve method so no call site carries an
  unreachable fallback arm
- Seed and push bond-verification dates from ConnectionManager instead of
  the provider closure, keyed off the store's bond slot since a WiFi
  connection overwrites the connection slot without touching the bond
- Drop the in-memory verification in clearPersistedConnection so a
  forget/re-pair can't inherit stale grace
- Merge the three stringly diagnostic properties into one linkDiagnostics
  snapshot read in a single actor hop; BLEPhaseKind keeps log text intact
- Port the pure classification suites to ReconnectPolicyTests; the
  driven-machine suites stay as integration coverage
- give flood timeouts extra grace for the response's return trip across the mesh
- don't let the timeout tail overwrite a response that lands right at sleep expiry
- re-present the discovery alert when a response arrives while the timeout
  alert is up; alert text is frozen at presentation
- add view model tests for discovery response handling
- match the response's key prefix against the open contact before
  resolving the view's discovery, so a straggler from a discovery on
  another contact can't finish this one
- an empty wire prefix never matches rather than matching every key
- Drop 110 unused SwiftGen keys across all 9 locales
- Remove 4 stringsdict-only plurals; delete empty Chats.stringsdict
- Keep live partial danger-zone string and Onboarding region.title
- Regenerate L10n.swift
- Ship it.lproj for app and widget string families
- List Italian in TRANSLATIONS.md and Intent locale tests
Firmware's suggested timeout only models the direct path, but repeater
replies flood until a return path is learned. Floor status/telemetry/
neighbor timeouts at 12s (per SessionConfiguration, so tests can pass 0).
hansimgamr and others added 29 commits July 20, 2026 07:14
* Add contact profile pictures

Contacts can now have a custom profile picture set from Photos or
Files, overriding the auto-generated initials avatar.

Co-authored-by: romeo <romeo@romeos-Mac-mini.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
- Stopping a session cancels its receive loop, which killed the
  transport's shared AsyncStream storage. Every rebuild over a live
  link then read a dead stream and timed out its handshake, so
  auto-reconnect stayed down until the app came to the foreground
- BLEStateMachine.renewDataStream() re-vends the stream; rebuildSession
  calls transport.refreshDataStream() between stopping the old session
  and building the new one
- New regression gate rebuilds over a single-slot responder transport
  and fails without the refresh call
- Forget clears only that radio's bond stamp; holder-matched store clear keeps other radios' shields
- Keep a live GATT link through a few app-layer rebuild failures so background recovery can still wake
- Refresh bond age from RSSI only while the app session is live; never create a stamp from RSSI alone
- Cover preserve budget, claim single-flight, and dead-stack bond-shield gates in tests
- present the first snapshot only when the on-screen item was baked with
  the divider row, not when the divider message id is in the item index
- skip the divider when an open staged zero unread: it already presented
  at the bottom, and a late divider grows a visible row and pushes the
  last message under the input bar
- firstSnapshotDecision takes a scalar dividerRowOnScreen fact; the
  call site computes it from items and the index map
- pagination tests now stage the open like ChatConversationView does
- Point path contact load at offlineDataStore instead of live services
- Clear hop-name caches when the store is nil
- Seed the nil-store unit test so clear is actually asserted
- Add both packages to the Settings Acknowledgements list
- Include Apache 2.0 (MessagingUI) and BSD 2-Clause (MapLibre) license texts
- Derive PSK from secretless #name URLs; explicit secret still wins
- Optional region_scope as local flood preference only
- Share/QR always emit name+secret; region only for .region
- Tests for golden hashes, precedence, and existing-channel open
Mark Italian as human-verified (corradoignoti #376), pin Chinese to PR #225, and list languages alphabetically.
- Drop txPower state, UI row, and get/set tx CLI calls
- Load and apply radio with get/set radio only
- Update footer and apply-failed copy in all locales
- Cover the single-command path in view model tests
- Add Settings → Maps for basemap appearance and the show-discovered toggle
- Union Discover pins with nodes on the map, with callout Add and coord checks
- Back up the new prefs and cover load, appearance, and import in tests
Make multi-hop status, telemetry, neighbours, and login reliable by
retransmitting inside the waiter instead of path-reset/flood fallback
and late salvage.

- Retransmit binary requests until a reply or the overall budget elapses
- Space resends at max(floor, 2× firmware suggestedTimeoutMs)
- Accept only the latest messageSent tag (companion single pending slot)
- Prefer dedicated status/telemetry commands where available
- Timeout is terminal for the section: no salvage-to-UI or grace spinner
- Map MeshCoreError.timeout to RemoteNodeError.timeout for section errors
- Keep one-shot path reset only for CLI
- Remove per-row dwell prewarm that started and cancelled tasks on every LazyVStack cell appear
- Keep top-N warm after load so recent chats still open settled
- Add regression tests for no per-row warm task and hosted scroll budget
- Treat timeout as terminal; do not re-present success after failure
- Wait at least 20s; flood grace 8s for return-leg jitter
- Resend while waiting at max(5s, 2× firmware est)
- Cover terminal late response and budget math in tests
- Add Support me above Continue; open Support settings then dismiss
- Push Settings detail on compact from selectedSetting
- Ship 1.3 notes: improved chats, contact photos, discovered nodes
- Drop pre-1.3 keys; translate all new strings
- Present a sheet when a tip or All Themes purchase verifies
- Gate on purchase returning true only; remove tip checkmark flash
- Localize title and body in all 10 locales
- Channel outgoing rows no longer force the new-sender flag
- Same-cluster follow-ups use 2pt top padding on both sides
- Cover consecutive outgoing in DisplayFlagsTests
- Add Filter menu on main, trace path, and neighbor SNR maps
- Persist MapFilterState per host; migrate the old discovered toggle
- Re-filter warm pin caches without a full reload
- Keep path hops visible under filters; bind hops by full key first
- Replace richerMaps with mapFilters in all locales
- Focus copy on favorites, discovered nodes, and node type
- Open GitHub Issues or email from Settings → About → Feedback
- Move diagnostics log export onto that screen
- Add Feedback strings in all locales
- Add releaseNotesURL on WhatsNewRelease
- Show Full release notes link on the sheet
- Point v1.3 catalog entry at the GitHub tag
@Avi0n
Avi0n merged commit 3cd0854 into main Jul 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants