diff --git a/Meshtastic/Resources/docs/index.json b/Meshtastic/Resources/docs/index.json
index a93255a2b..729ea5224 100644
--- a/Meshtastic/Resources/docs/index.json
+++ b/Meshtastic/Resources/docs/index.json
@@ -292,24 +292,24 @@
"orange",
"markdown",
"delimiters",
+ "conversation",
+ "tap",
"retry",
"exclamationmark",
- "tap",
"recipient",
"node",
"meshtastic",
- "conversation",
"after",
"selection",
+ "links",
"bold",
"radio",
"public",
- "links",
"field",
"button",
"symbol"
],
- "charCount": 11883
+ "charCount": 12005
},
{
"id": "mqtt",
diff --git a/Meshtastic/Resources/docs/markdown/user/messages.md b/Meshtastic/Resources/docs/markdown/user/messages.md
index c4e4633e2..ba3955c49 100644
--- a/Meshtastic/Resources/docs/markdown/user/messages.md
+++ b/Meshtastic/Resources/docs/markdown/user/messages.md
@@ -182,7 +182,7 @@ On iOS 18 and later, formatting buttons appear in the compact toolbar below the
When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden.
-Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance.
+Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble.
| Example | Description |
|---------|-------------|
diff --git a/Meshtastic/Resources/docs/user/messages.html b/Meshtastic/Resources/docs/user/messages.html
index b05ec3d62..cadb54bdb 100644
--- a/Meshtastic/Resources/docs/user/messages.html
+++ b/Meshtastic/Resources/docs/user/messages.html
@@ -317,7 +317,7 @@
How to Format Text
Live Preview
When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden.
-Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance.
+Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble.
diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift
index 274d5a913..2abed1e4e 100644
--- a/Meshtastic/Views/Messages/ChannelList.swift
+++ b/Meshtastic/Views/Messages/ChannelList.swift
@@ -113,7 +113,7 @@ struct ChannelList: View {
if hasMessages {
HStack(alignment: .top) {
- Text(LocalizedStringKey(mostRecent?.messagePayload ?? " "))
+ MessagePreviewText(mostRecent?.messagePayload ?? " ")
.font(.footnote)
.foregroundColor(.onSurfaceVariant)
}
diff --git a/Meshtastic/Views/Messages/MessagePreviewText.swift b/Meshtastic/Views/Messages/MessagePreviewText.swift
new file mode 100644
index 000000000..bccd88359
--- /dev/null
+++ b/Meshtastic/Views/Messages/MessagePreviewText.swift
@@ -0,0 +1,33 @@
+// MARK: MessagePreviewText
+
+import SwiftUI
+
+struct MessagePreviewText: View {
+
+ private let text: AttributedString
+
+ init(_ source: String) {
+ text = Self.attributedString(for: source)
+ }
+
+ var body: some View {
+ Text(text)
+ }
+
+ static func attributedString(for source: String) -> AttributedString {
+ guard var result = try? AttributedString(
+ markdown: source,
+ options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
+ ) else {
+ return AttributedString(source)
+ }
+
+ let linkRanges = result.runs.compactMap { run in
+ run.link == nil ? nil : run.range
+ }
+ for range in linkRanges {
+ result[range].link = nil
+ }
+ return result
+ }
+}
diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift
index f7769dd6e..db7f153f1 100644
--- a/Meshtastic/Views/Messages/UserList.swift
+++ b/Meshtastic/Views/Messages/UserList.swift
@@ -315,7 +315,7 @@ private struct DirectMessageUserRow: View {
if let summary {
HStack(alignment: .top) {
- Text(LocalizedStringKey(summary.payload))
+ MessagePreviewText(summary.payload)
.font(.footnote)
.foregroundColor(.onSurfaceVariant)
}
diff --git a/MeshtasticTests/MessagePreviewTextTests.swift b/MeshtasticTests/MessagePreviewTextTests.swift
new file mode 100644
index 000000000..1c59dd329
--- /dev/null
+++ b/MeshtasticTests/MessagePreviewTextTests.swift
@@ -0,0 +1,27 @@
+import Foundation
+import Testing
+
+@testable import Meshtastic
+
+@Suite("Message preview text")
+struct MessagePreviewTextTests {
+
+ @Test("Bare URLs are not interactive in conversation previews")
+ func bareURLIsNotInteractive() {
+ let source = "See https://meshtastic.org/docs for details"
+ let preview = MessagePreviewText.attributedString(for: source)
+
+ #expect(String(preview.characters) == source)
+ #expect(preview.runs.allSatisfy { $0.link == nil })
+ }
+
+ @Test("Markdown links keep their label without remaining interactive")
+ func markdownLinkIsNotInteractive() {
+ let preview = MessagePreviewText.attributedString(
+ for: "Read the [Meshtastic docs](https://meshtastic.org/docs)"
+ )
+
+ #expect(String(preview.characters) == "Read the Meshtastic docs")
+ #expect(preview.runs.allSatisfy { $0.link == nil })
+ }
+}
diff --git a/docs/user/messages.md b/docs/user/messages.md
index c4e4633e2..ba3955c49 100644
--- a/docs/user/messages.md
+++ b/docs/user/messages.md
@@ -182,7 +182,7 @@ On iOS 18 and later, formatting buttons appear in the compact toolbar below the
When the compose field contains markdown syntax, a preview bubble appears above the compose field showing how the message will look when sent. The preview updates in real time as you type. When no markdown is present, the preview is hidden.
-Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance.
+Markdown formatting is also rendered in the channel and user message list previews, so you can see formatted text at a glance. Links in these conversation-list previews are not interactive; open the conversation to tap a link in its message bubble.
| Example | Description |
|---------|-------------|