Skip to content

2.7.7 Working Changes#1551

Merged
garthvh merged 29 commits into
mainfrom
2.7.7
Jan 15, 2026
Merged

2.7.7 Working Changes#1551
garthvh merged 29 commits into
mainfrom
2.7.7

Conversation

@garthvh

@garthvh garthvh commented Jan 8, 2026

Copy link
Copy Markdown
Member

No description provided.

garthvh and others added 12 commits January 4, 2026 20:06
update the translations
* Don't add new BLE  devices to the device list in the backgournd

* Bump version

* Update Meshtastic/MeshtasticApp.swift

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Meshtastic/MeshtasticApp.swift

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bumps the app version to 2.7.7 and introduces a new emoji picker interface for tapback reactions along with several improvements and clarifications.

Key Changes

  • Replaces the static tapback menu with a dynamic emoji picker that allows users to select any emoji as a reaction
  • Adds background state tracking to the AccessoryManager
  • Clarifies the node purging explanation text in app settings

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Meshtastic/Views/Messages/TapbackInputView.swift New view implementing emoji-only text field with automatic keyboard handling and emoji extraction logic
Meshtastic/Views/Messages/MessageText.swift Integrates TapbackInputView sheet with emoji selection callback and message sending
Meshtastic/Views/Messages/MessageContextMenuItems.swift Simplifies tapback menu from static options to button that opens emoji picker
Meshtastic/Helpers/EmojiOnlyTextField.swift Enhances emoji text field with first responder management and keyboard type change detection
Meshtastic/Views/Settings/AppSettings.swift Updates node purging description for better clarity
Meshtastic/MeshtasticApp.swift Adds background state tracking when scene phase changes
Meshtastic/Helpers/MeshPackets.swift Removes alert configuration from Live Activity updates
Meshtastic/Extensions/UserDefaults.swift Adds purgeStaleNodeDays property for node purging configuration
Meshtastic.xcodeproj/project.pbxproj Updates project files and version numbers to 2.7.7
Localizable.xcstrings Updates localization strings for the new description text

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Meshtastic/Views/Messages/TapbackInputView.swift Outdated
Comment thread Meshtastic/Views/Messages/MessageText.swift Outdated
Comment thread Meshtastic/Helpers/EmojiOnlyTextField.swift Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

@garthvh I've opened a new pull request, #1552, to work on those changes. Once the pull request is ready, I'll request review from you.

garthvh and others added 5 commits January 7, 2026 16:43
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan

* Remove nested Task block in tapback handler

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
@CLAassistant

CLAassistant commented Jan 9, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
8 out of 9 committers have signed the CLA.

✅ MGJ520
✅ garthvh
✅ RCGV1
✅ thebentern
✅ matkam
✅ bhardie
✅ compumike
✅ alvarosamudio
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

@garthvh garthvh requested a review from Copilot January 9, 2026 07:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.presentationDetents([.large])
}
.sheet(isPresented: $editingSettings) {
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs)

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed .presentationDetents([.large]) modifier may have been intentional for the MapSettingsForm. Without it, the sheet will use default presentation sizing which may differ from the explicit .large detent that was removed. Consider whether this change was intended or if the modifier should remain.

Suggested change
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs)
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs)
.presentationDetents([.large])

Copilot uses AI. Check for mistakes.
Comment thread Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift
if value.count > 1 {
let index = value.index(value.startIndex, offsetBy: 1)
icon = String(value[index])
}

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed iconIsFocused = false line means the keyboard will remain visible after emoji selection, which differs from the previous behavior where the keyboard was automatically dismissed. This may be unintended since users typically expect the keyboard to dismiss after selecting an emoji.

Suggested change
}
}
iconIsFocused = false

Copilot uses AI. Check for mistakes.
import UIKit

struct TapbackInputView: View {
@Binding var text: String

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The view has a text binding that gets cleared after use (line 28), but this creates unnecessary state management complexity. Consider removing the binding and using a @State variable internally instead, since the parent doesn't need to track the text value.

Suggested change
@Binding var text: String
@State private var text: String = ""

Copilot uses AI. Check for mistakes.
return
}

_ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context)

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of clearStaleNodes is explicitly discarded with _=, but this operation is called synchronously during the critical path of config requests. Consider whether this should be moved to a background task to avoid blocking the config request flow.

Suggested change
_ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context)
Task.detached(priority: .background) {
_ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context)
}

Copilot uses AI. Check for mistakes.

let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index)
try await send(toRadio, debugDescription: logString)
channelPacket(channel: chan, fromNum: self.activeDeviceNum ?? 0, context: context)

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added channelPacket call is inside the channel sending loop. This means it's being called for every channel in the set, which could result in multiple database saves. Consider whether this should be called once after all channels are sent, or if the current behavior is intentional for immediate feedback.

Copilot uses AI. Check for mistakes.
* Add file missing from project, must have merged badly

* Remove ui kit emoji keyboard
@garthvh garthvh requested a review from Copilot January 15, 2026 17:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

try visitor.visitSingularBoolField(value: v, fieldNumber: 100)
}()
case nil: break
default: break

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from case nil: break to default: break in the switch statement handling payloadVariant is inconsistent with Swift best practices for handling optional enum cases. When dealing with optional enums in a switch, using case nil: is more explicit and makes the intent clearer that we're handling the case where the variant is not set. Using default: could inadvertently catch new cases added to the enum in the future.

Suggested change
default: break
case nil: break

Copilot uses AI. Check for mistakes.

let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index)
try await send(toRadio, debugDescription: logString)
channelPacket(channel: chan, fromNum: self.activeDeviceNum ?? 0, context: context)

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to channelPacket is made for every channel in the loop without checking if self.activeDeviceNum is nil. If activeDeviceNum is nil, this will pass 0 to the function. Based on the channelPacket function definition, it attempts to fetch MyInfo using this number with predicate myNodeNum == %lld. Passing 0 could result in unexpected behavior or failure to save the channel properly. Consider unwrapping activeDeviceNum and handling the nil case appropriately before the loop.

Copilot uses AI. Check for mistakes.
// Check if this is a continuation (variation selector, skin tone modifier, zero-width joiner, etc.)
if let scalar = nextChar.unicodeScalars.first,
(scalar.properties.isVariationSelector ||
scalar.value == 0xFE0F || // Variation selector

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition checks for scalar.properties.isVariationSelector and then explicitly checks for 0xFE0F which is also a variation selector. This creates redundancy since isVariationSelector should already cover the 0xFE0F case. Consider removing the explicit 0xFE0F check to avoid duplication.

Suggested change
scalar.value == 0xFE0F || // Variation selector

Copilot uses AI. Check for mistakes.
Comment on lines +461 to +462
if fetchedMyInfo.count != 1 {
throw AccessoryError.appError("MyInfo not found")

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message 'MyInfo not found' when fetchedMyInfo.count != 1 is ambiguous. It doesn't distinguish between the case where no MyInfo was found (count == 0) versus multiple MyInfo entries were found (count > 1). Consider providing a more specific error message such as 'MyInfo not found for device' when count is 0, or 'Multiple MyInfo entries found for device' when count > 1.

Suggested change
if fetchedMyInfo.count != 1 {
throw AccessoryError.appError("MyInfo not found")
if fetchedMyInfo.count == 0 {
throw AccessoryError.appError("MyInfo not found for device \(deviceNum)")
} else if fetchedMyInfo.count > 1 {
throw AccessoryError.appError("Multiple MyInfo entries found for device \(deviceNum)")

Copilot uses AI. Check for mistakes.
* Make BLE Transport an actor to fix background discovery crashes

* Protobufs

* Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Throw too many retries error again, remove return

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@garthvh garthvh requested a review from Copilot January 15, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 354 to 355
isRunning = false
return
throw AccessoryError.tooManyRetries

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing the unreachable return statement on line 354, the code now correctly throws the error, but the isRunning = false statement on line 354 will never execute because the throw on line 355 immediately exits the function. The isRunning flag should be set to false before throwing the error to ensure proper cleanup.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to 125
TextField("Select an emoji", text: $icon)
.keyboardType(.emoji)
.font(.title)
.focused($iconIsFocused)
.onChange(of: icon) { _, value in

// If you have anything other than emojis in your string make it empty
if !value.onlyEmojis() {
icon = ""
}
// If a second emoji is entered delete the first one
if value.count >= 1 {

if value.count > 1 {
let index = value.index(value.startIndex, offsetBy: 1)
icon = String(value[index])
}
iconIsFocused = false
}
}

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emoji validation logic has been simplified but no longer validates that the input is actually an emoji. The previous implementation used onlyEmojis() to ensure only emoji characters were entered. Now any character can be entered. Consider adding validation to ensure only emoji characters are accepted, or document this behavior change.

Copilot uses AI. Check for mistakes.
.scrollDismissesKeyboard(.immediately)
.focused($isTapbackInputFocused)
.frame(width: 0, height: 0)
.opacity(0)

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hidden TextField used for emoji input has no accessibility label or hint. Users relying on screen readers won't understand the purpose of this focused element. Add an .accessibilityLabel() and .accessibilityHint() to explain that this is for adding a tapback reaction emoji.

Suggested change
.opacity(0)
.opacity(0)
.accessibilityLabel("Tapback emoji input")
.accessibilityHint("Type an emoji to add a tapback reaction to this message.")

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
static var emoji: UIKeyboardType {
return UIKeyboardType(rawValue: 124) ?? .default

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a magic number (124) for the emoji keyboard type is fragile and undocumented by Apple. This private API usage could break in future iOS versions. Consider documenting why this value is used, adding a comment about the risk, or checking for availability on specific iOS versions where this has been tested.

Suggested change
static var emoji: UIKeyboardType {
return UIKeyboardType(rawValue: 124) ?? .default
/// Best-effort access to the emoji keyboard type.
///
/// Apple does not expose a public `UIKeyboardType` case for the emoji keyboard.
/// Historically, the emoji keyboard has used the raw value `124`, which is not
/// documented and may change in future iOS versions. If the raw value is not
/// valid on a given OS version, this property safely falls back to `.default`.
///
/// This should be treated as a convenience only and not relied on for
/// correctness-critical behavior.
private static let emojiKeyboardRawValue: Int = 124
static var emoji: UIKeyboardType {
return UIKeyboardType(rawValue: emojiKeyboardRawValue) ?? .default

Copilot uses AI. Check for mistakes.
@garthvh garthvh requested a review from Copilot January 15, 2026 21:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

///
/// Bitfield for storing booleans.
/// LSB 0 is_key_manually_verified
/// LSB 1 is_muted

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'Persistes' to 'Persists' in the comment on line 2974 of mesh.pb.swift.

Copilot uses AI. Check for mistakes.
case meshstick1262 // = 121

///
/// LilyGo T-Beam 1W

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing whitespace at the end of the comment line.

Suggested change
/// LilyGo T-Beam 1W
/// LilyGo T-Beam 1W

Copilot uses AI. Check for mistakes.

// Transport properties
var supportsManualConnection: Bool = false
let supportsManualConnection: Bool = false

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property 'supportsManualConnection' was changed from 'var' to 'let', but the class was also converted to an actor. Since this is now a constant, consider evaluating if this property should be static or if it needs to be an instance property at all.

Copilot uses AI. Check for mistakes.
@garthvh garthvh merged commit 3eef389 into main Jan 15, 2026
6 of 8 checks passed
@garthvh garthvh deleted the 2.7.7 branch January 15, 2026 22:13
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.