Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e71d810
fix: hot-swap TCP interfaces without disturbing the others
torlando-tech Apr 30, 2026
a42a9a1
fix: hot-swap TCP interfaces without disturbing the others
torlando-tech Apr 30, 2026
07619d5
feat: multi-TCP tunnel — extension manages a connection per entity
torlando-tech Apr 30, 2026
5d3338d
chore: extension diag logs for TCP config/state changes
torlando-tech Apr 30, 2026
42ddb6e
feat(InterfaceManagement): add TCP client community-server wizard
torlando-agent[bot] May 5, 2026
4fa8abd
fix(MicronParser): persist formatting state across lines (#63)
torlando-agent[bot] May 6, 2026
8ff5967
fix(TCPClientWizard): mirror android server list, drop bootstrap split
torlando-agent[bot] May 6, 2026
f8f8e72
feat(auto-announce): granular trigger toggles + new wiring
torlando-agent[bot] May 7, 2026
af2f758
chore: bump reticulum-swift pin to 0.2.4
torlando-agent[bot] May 8, 2026
ba598d0
fix(AppServices): only resetTimer when announce was actually sent
torlando-agent[bot] May 8, 2026
c8baec7
test(auto-announce): extract AutoAnnouncePolicy + cover trigger gates
torlando-agent[bot] May 8, 2026
f0dcb95
fix(auto-announce): attribute peer-child connected events to peer-spa…
torlando-agent[bot] May 8, 2026
c22080d
fix(auto-announce): make peer-child attribution race-free
torlando-agent[bot] May 8, 2026
85331fa
Merge pull request #70 from torlando-tech/feat/auto-announce-granular…
torlando-tech May 8, 2026
50ef717
chore(greptile): iteration 1 — applied 2, rejected 0
torlando-agent[bot] May 8, 2026
792ebd4
chore(greptile): iteration 1 — applied 1, rejected 0
torlando-agent[bot] May 8, 2026
2e4cda2
chore(greptile): iteration 2 — applied 1, rejected 0
torlando-agent[bot] May 8, 2026
23a37c5
feat(Map): follow app dark mode for OpenFreeMap style
torlando-agent[bot] May 5, 2026
3049a49
Update Sources/ColumbaApp/Views/Map/MapLibreMapView.swift
torlando-tech May 9, 2026
9ce7fe2
Merge pull request #65 from torlando-tech/columba-suite/issue-59-dark…
torlando-tech May 9, 2026
7e82132
Merge pull request #61 from torlando-tech/fix/tcp-interface-hot-swap
torlando-tech May 9, 2026
2646617
chore(greptile): iteration 1 — applied 4, rejected 0
torlando-agent[bot] May 9, 2026
371bb71
feat(InterfaceManagement): add TCP client community-server wizard (#64)
torlando-agent[bot] May 10, 2026
f75a66c
feat: add Maestro UI flows for columba-suite ui-screenshotter (#69)
torlando-tech May 10, 2026
77fbebb
chore(test): add debug-only iOS test surface for phone smoke-test pip…
torlando-tech May 10, 2026
acd4791
fix(test-harness): unbreak release-guard + add file-based event log
torlando-tech May 10, 2026
42057b8
test(harness): add lxma-test://dump_log for OSLogStore extraction
torlando-tech May 10, 2026
6836d1f
fix(harness): wire iOS PROPAGATED smoke end-to-end
torlando-tech May 10, 2026
68b776b
chore: bump LXMF-swift to a3e5b00 (DIRECT identify-drop fix)
torlando-tech May 10, 2026
23c8ca0
chore(deps): pin reticulum-swift to fix/link-data-no-header2-conversion
torlando-tech May 10, 2026
6443247
test(harness): add diagnostic ticker + screenshot capture to TestCont…
torlando-tech May 10, 2026
351ba4f
fix(prop): single checkmark + 'sent to relay' text + dump_db diag
torlando-tech May 10, 2026
f1833fa
chore(deps): bump LXMF-swift to 0.4.0 + reticulum-swift to 0.3.0
torlando-tech May 11, 2026
e65d410
chore(deps): bump LXMF-swift to 0.4.0 + reticulum-swift to 0.3.0 (#73)
torlando-tech May 11, 2026
a856b0a
Merge remote-tracking branch 'origin/main' into feat/multi-tcp-tunnel
torlando-tech May 11, 2026
0f7cf3e
Merge branch 'chore/phone-harness-stage1-ios' into feat/multi-tcp-tunnel
torlando-tech May 11, 2026
c0d2213
fix(tunnel): guard applyTunnelModeToInterfaces(active:false) against …
torlando-tech May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 61 additions & 5 deletions Columba.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ let package = Package(
// `.swiftpm/configuration/mirrors.json` mapping the URL to a local
// directory — see README "Local development against unreleased
// library changes" for the exact recipe.
.package(url: "https://github.com/torlando-tech/LXMF-swift.git", from: "0.3.0"),
.package(url: "https://github.com/torlando-tech/LXMF-swift.git", from: "0.4.0"),
.package(url: "https://github.com/torlando-tech/LXST-swift.git", from: "0.2.0"),
.package(url: "https://github.com/torlando-tech/reticulum-swift.git", from: "0.2.0"),
.package(url: "https://github.com/torlando-tech/reticulum-swift.git", from: "0.3.0"),
.package(url: "https://github.com/maplibre/maplibre-gl-native-distribution", from: "6.9.0"),
],
targets: [
Expand Down
18 changes: 18 additions & 0 deletions Sources/ColumbaApp/App/ColumbaApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ struct ColumbaApp: App {
.tint(Theme.accentColor)
.id(ThemeManager.shared.themeVersion)
.onOpenURL { url in
#if DEBUG
// Debug-only test-harness sibling scheme — `lxma-test://<action>?<query>`.
// See Sources/ColumbaApp/Test/TestURLHandler.swift. Compiled out
// entirely in release builds, AND `lxma-test` is not registered
// in CFBundleURLSchemes — so iOS won't route to this handler in
// release even if the file accidentally shipped.
if url.scheme == "lxma-test" {
_ = TestURLHandler.handle(url: url)
return
}
#endif
guard url.scheme == "lxma" else { return }
pendingDeepLink = url.absoluteString
}
Expand Down Expand Up @@ -519,6 +530,13 @@ struct RootView: View {

self.isInitialized = true

#if DEBUG
// Wire the test-harness surface to the live AppServices.
// No-op in release: the entire TestURLHandler / TestController
// graph is `#if DEBUG`-gated.
TestURLHandler.bind(appServices: appServices)
#endif

// DEBUG: Auto-trigger propagation sync on launch for testing
if ProcessInfo.processInfo.arguments.contains("--auto-sync") {
let services = appServices
Expand Down
27 changes: 20 additions & 7 deletions Sources/ColumbaApp/Models/MicronParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public struct MicronParser {
var literalLines: [String] = []
var currentIndent = 0
var currentAlignment: MicronAlignment = .left
// Formatting state persists across lines (matches python NomadNet's
// MicronParser, where `!/`*/`_/`Fxxx/`Bxxx are document-scoped until
// toggled off or reset). Without this the chat-room page's
// `F0ff`B52f preamble drops its colors before the ASCII art.
var currentStyle: MicronTextStyle = .plain

// Parse headers from top of document
while lineIndex < lines.count {
Expand Down Expand Up @@ -68,7 +73,8 @@ public struct MicronParser {
if content.isEmpty {
continue
}
let (spans, alignment, fields) = parseInline(content, currentStyle: .plain, currentAlignment: currentAlignment)
let (spans, alignment, fields, updatedStyle) = parseInline(content, currentStyle: currentStyle, currentAlignment: currentAlignment)
currentStyle = updatedStyle
if let alignment = alignment { currentAlignment = alignment }
elements.append(.heading(level: headingLevel, spans: spans, alignment: currentAlignment))
for field in fields { elements.append(.formField(field)) }
Expand All @@ -83,12 +89,16 @@ public struct MicronParser {
continue
}

// Reset indent
// Reset indent — also resets formatting state to plain, matching
// python NomadNet's `<` semantics where the line restarts parsing
// from a default state.
if firstChar == "<" {
currentIndent = 0
currentStyle = .plain
let rest = String(line.dropFirst())
if !rest.isEmpty {
let (spans, alignment, fields) = parseInline(rest, currentStyle: .plain, currentAlignment: currentAlignment)
let (spans, alignment, fields, updatedStyle) = parseInline(rest, currentStyle: currentStyle, currentAlignment: currentAlignment)
currentStyle = updatedStyle
if let alignment = alignment { currentAlignment = alignment }
elements.append(.paragraph(spans: spans, alignment: currentAlignment, indentLevel: currentIndent))
for field in fields { elements.append(.formField(field)) }
Expand All @@ -112,7 +122,8 @@ public struct MicronParser {
}

// Regular paragraph — parse inline formatting
let (spans, alignment, fields) = parseInline(line, currentStyle: .plain, currentAlignment: currentAlignment)
let (spans, alignment, fields, updatedStyle) = parseInline(line, currentStyle: currentStyle, currentAlignment: currentAlignment)
currentStyle = updatedStyle
if let alignment = alignment { currentAlignment = alignment }
elements.append(.paragraph(spans: spans, alignment: currentAlignment, indentLevel: currentIndent))
for field in fields { elements.append(.formField(field)) }
Expand Down Expand Up @@ -142,12 +153,14 @@ public struct MicronParser {
// MARK: - Inline Parsing

/// Parse inline formatting within a line of text.
/// Returns parsed spans, any alignment change detected, and any form fields found.
/// Returns parsed spans, any alignment change detected, any form fields found,
/// and the formatting style at the end of the line so callers can carry it
/// forward (matches python NomadNet's document-scoped formatting state).
private static func parseInline(
_ text: String,
currentStyle: MicronTextStyle,
currentAlignment: MicronAlignment
) -> ([MicronSpan], MicronAlignment?, [MicronFormField]) {
) -> ([MicronSpan], MicronAlignment?, [MicronFormField], MicronTextStyle) {
var spans: [MicronSpan] = []
var style = currentStyle
var alignment: MicronAlignment? = nil
Expand Down Expand Up @@ -321,7 +334,7 @@ public struct MicronParser {
}

flushBuffer()
return (spans, alignment, formFields)
return (spans, alignment, formFields, style)
}

// MARK: - Form Field Parsing
Expand Down
45 changes: 31 additions & 14 deletions Sources/ColumbaApp/Models/TcpCommunityServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,41 @@ struct TcpCommunityServer: Identifiable {
extension TcpCommunityServer {
/// Curated list of public Reticulum transport nodes.
///
/// Sourced from Android Columba's `TcpCommunityServers.kt`.
/// Bootstrap servers are preferred for first-time connections.
/// Sourced from Android Columba's `TcpCommunityServer.kt`. Keep this list
/// in sync with `app/src/main/java/network/columba/app/data/model/TcpCommunityServer.kt`.
/// Up-to-date community directories: directory.rns.recipes, rmap.world.
static let servers: [TcpCommunityServer] = [
// Bootstrap servers
// Bootstrap-class servers (well-established, reliable nodes).
// Reticulum-Swift does not yet support the bootstrap interface mode,
// so the iOS UI surfaces these alongside other community servers.
TcpCommunityServer(name: "Beleth RNS Hub", host: "rns.beleth.net", port: 4242, isBootstrap: true),
TcpCommunityServer(name: "Quad4 RNS", host: "rns.quad4.io", port: 4242, isBootstrap: true),
TcpCommunityServer(name: "FireZen Hub", host: "reticulum.firezen.xyz", port: 4242, isBootstrap: true),
TcpCommunityServer(name: "Quad4 TCP Node 1", host: "rns.quad4.io", port: 4242, isBootstrap: true),
TcpCommunityServer(name: "FireZen", host: "firezen.com", port: 4242, isBootstrap: true),

// Community servers
TcpCommunityServer(name: "RNS Amsterdam", host: "amsterdam.connect.reticulum.network", port: 4965, isBootstrap: false),
TcpCommunityServer(name: "RNS BetweenTheBorders", host: "betweentheborders.com", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "RNS Frankfurt", host: "frankfurt.connect.reticulum.network", port: 5377, isBootstrap: false),
TcpCommunityServer(name: "i2p Reticulum", host: "uxg5a4t3pnif7zoo43fkdrhgamlbfcovgsrzjakqab3pxjfqwdcq.b32.i2p", port: 5001, isBootstrap: false),
TcpCommunityServer(name: "Reticulum Ireland", host: "reticulum.liamcottle.net", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "TheHub", host: "thehub.duckdns.org", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "Kosciuszko", host: "kosciuszko.au.int.rns.directory", port: 9696, isBootstrap: false),
TcpCommunityServer(name: "Reticulum Ireland v2", host: "reticulum.liamcottle.net", port: 4343, isBootstrap: false),
TcpCommunityServer(name: "RNS Roaming", host: "roaming.int.rns.directory", port: 9697, isBootstrap: false),
TcpCommunityServer(name: "g00n.cloud Hub", host: "dfw.us.g00n.cloud", port: 6969, isBootstrap: false),
TcpCommunityServer(name: "interloper node", host: "intr.cx", port: 4242, isBootstrap: false),
TcpCommunityServer(
name: "interloper node (Tor)",
host: "intrcxv4fa72e5ovler5dpfwsiyuo34tkcwfy5snzstxkhec75okowqd.onion",
port: 4242,
isBootstrap: false
),
TcpCommunityServer(name: "Jon's Node", host: "rns.jlamothe.net", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "noDNS1", host: "202.61.243.41", port: 4965, isBootstrap: false),
TcpCommunityServer(name: "noDNS2", host: "193.26.158.230", port: 4965, isBootstrap: false),
TcpCommunityServer(name: "NomadNode SEAsia TCP", host: "rns.jaykayenn.net", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "0rbit-Net", host: "93.95.227.8", port: 49952, isBootstrap: false),
TcpCommunityServer(name: "Quad4 TCP Node 2", host: "rns2.quad4.io", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "Quortal TCP Node", host: "reticulum.qortal.link", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "R-Net TCP", host: "istanbul.reserve.network", port: 9034, isBootstrap: false),
TcpCommunityServer(name: "RNS bnZ-NODE01", host: "node01.rns.bnz.se", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "RNS COMSEC-RD", host: "80.78.23.249", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "RNS HAM RADIO", host: "135.125.238.229", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "RNS Testnet StoppedCold", host: "rns.stoppedcold.com", port: 4242, isBootstrap: false),
TcpCommunityServer(name: "RNS_Transport_US-East", host: "45.77.109.86", port: 4965, isBootstrap: false),
TcpCommunityServer(name: "SparkN0de", host: "aspark.uber.space", port: 44860, isBootstrap: false),
TcpCommunityServer(name: "Tidudanka.com", host: "reticulum.tidudanka.com", port: 37500, isBootstrap: false),
]

/// Default server for first-time connections.
Expand Down
20 changes: 20 additions & 0 deletions Sources/ColumbaApp/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@
<string>lxma</string>
</array>
</dict>
<!-- lxma-test is the debug-only test-harness scheme used by the
phone smoke-test orchestrator (see
Sources/ColumbaApp/Test/TestURLHandler.swift).
The Swift dispatcher is `#if DEBUG`-gated so this scheme
is a no-op in release builds — registering the scheme
here is the simplest cross-config path on a project
without per-config Info.plist variants. iOS will route
`lxma-test://` URLs to a release Columba.app and the
release `.onOpenURL` handler will fall through with
no registered action. -->
<dict>
<key>CFBundleURLName</key>
<string>network.columba.Columba.lxma-test</string>
<key>CFBundleURLSchemes</key>
<array>
<string>lxma-test</string>
</array>
</dict>
</array>
<key>NSBluetoothWhenInUseUsageDescription</key>
<string>Columba uses Bluetooth for peer-to-peer mesh networking and connecting to RNode radio devices.</string>
Expand All @@ -28,6 +46,8 @@
<key>UIAppFonts</key>
<array>
<string>materialdesignicons.ttf</string>
<string>JetBrainsMono-Regular.ttf</string>
<string>JetBrainsMono-Bold.ttf</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
Expand Down
Binary file not shown.
Binary file not shown.
Loading