Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2e49820
RNS core + protocol seam (dual-backend, PR 1/3)
torlando-agent[bot] May 30, 2026
7be8fb4
address greptile review feedback (greploop iteration 1)
torlando-agent[bot] May 30, 2026
19bde2f
fix(rns): clear RNS.Reticulum singleton in reset_identity (greploop i…
torlando-agent[bot] May 30, 2026
59ed162
fix(rns): full teardown in reset_identity + identify after link is ac…
torlando-agent[bot] May 30, 2026
3814427
fix(rns): register link in _links before wiring callbacks (greploop i…
torlando-agent[bot] May 30, 2026
026dc78
fix(rns): real propagationSync received count on Swift backend
torlando-agent[bot] May 30, 2026
42f6822
fix(bridge): run propagationSync off the shared serial queue
torlando-agent[bot] May 30, 2026
63bfa96
fix: three correctness defects from greptile review (#79 iter)
torlando-agent[bot] May 30, 2026
71bb911
fix: bound propagationSync by timeout, pin ble-reticulum, fetch wheel…
torlando-agent[bot] May 30, 2026
b7db7fe
fix: backend restart path — drain loop + leaked per-link state tasks
torlando-agent[bot] May 30, 2026
4b365fd
fix: reset_identity must drain RNS.Transport class state (mirror stop())
torlando-agent[bot] May 30, 2026
cc2c28f
fix: don't hold _lock across link.teardown() in stop()/reset_identity
torlando-agent[bot] May 30, 2026
ef9cd89
fix: clear RNode callbacks on teardown + canonical LXMF announce app_…
torlando-agent[bot] May 31, 2026
422227d
fix: pr1 teardown/lifecycle correctness (greptile #79 round 2)
torlando-agent[bot] May 31, 2026
99e04e6
fix: serialize SwiftRNSBackend link registry against openLink races
torlando-agent[bot] May 31, 2026
4e56d93
fix: register link + stateTask atomically in openLink
torlando-agent[bot] May 31, 2026
8ced444
fix: guard interfaceIds with linkLock + cancel stale announcePoller
torlando-agent[bot] May 31, 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
26 changes: 25 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ jobs:
git clone https://github.com/torlando-tech/LXMF-swift.git ../LXMF-swift
git clone https://github.com/torlando-tech/LXST-swift.git ../LXST-swift

# The Python-backend (`Columba`) scheme links Python.xcframework + the
# bundled wheels; without these the build phase that copies them fails.
# Fetch them before any xcodebuild step (pinned deps → reproducible).
- name: Fetch Python framework + wheels
run: |
support/fetch-python.sh
support/fetch-wheels.sh

- name: Select Xcode
run: |
XCODE=$(ls -d /Applications/Xcode_16*.app 2>/dev/null | sort -V | tail -1)
Expand All @@ -37,7 +45,7 @@ jobs:
echo "device_id=$DEVICE_ID" >> "$GITHUB_OUTPUT"
echo "Using simulator: $DEVICE_ID"

- name: Build
- name: Build (Python backend — default)
run: |
xcodebuild build \
-project Columba.xcodeproj \
Expand All @@ -50,6 +58,22 @@ jobs:
DEVELOPMENT_TEAM="" \
PROVISIONING_PROFILE_SPECIFIER=""
Comment thread
torlando-tech marked this conversation as resolved.

# Verify the native reticulum-swift/LXMF-swift backend variant also builds
# (COLUMBA_BACKEND_SWIFT). The UI/AppServices are backend-agnostic, so this
# guards against the Swift backend or its seam drifting out of compile.
- name: Build (Swift-native backend)
run: |
xcodebuild build \
-project Columba.xcodeproj \
-scheme Columba-Swift \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.device_id }}" \
CODE_SIGN_IDENTITY=- \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
PROVISIONING_PROFILE_SPECIFIER=""

- name: Build and run tests
run: |
xcodebuild test \
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ xcuserdata/
*.dSYM
timeline.xctimeline
playground.xcworkspace
build/

# Swift Package Manager
.build/
Expand All @@ -27,3 +28,16 @@ package-lock.json

# Local Xcode signing overrides
Config/LocalSigning.xcconfig

# Embedded Python (fetched by support/fetch-python.sh — too large for git)
Frameworks/Python.xcframework/
Frameworks/Python-3.13-iOS-support.*.tar.gz
Frameworks/VERSIONS

# iOS Python wheels (fetched by support/fetch-wheels.sh)
wheels-iphoneos/
wheels-iphonesimulator/

# Python build artifacts
__pycache__/
*.pyc
48 changes: 48 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Columba-iOS Architecture

Target / module dependency graph for the iOS app. Mirrors the role of `docs/architecture.md` in the sibling Android repo (`columba/`).

Regenerate this file from the current `Package.swift` + `Columba.xcodeproj/project.pbxproj`:

```sh
ruby support/generate-module-graph.rb
```

The script reads:
- pbxproj targets (e.g. `ColumbaApp`, `ColumbaNetworkExtension`, `PythonBridge`, `RNSBackendPy`) via the `xcodeproj` Ruby gem (already a dependency for `support/configure-xcodeproj.rb`).
- SPM targets (e.g. `RNSAPI`, `LXSTSwift`, `COpus`, `CCodec2`, `SwiftBLEBridge`) via `swift package dump-package`.

It overwrites the Mermaid block between the start/end marker comments below the `## Target Graph` heading. Do not edit between the markers by hand — changes will be lost on next regen.

## Target Graph

<!-- module-graph-start -->
```mermaid
flowchart TD
CCodec2["CCodec2"]
COpus["COpus"]
ColumbaApp["ColumbaApp"]
ColumbaNetworkExtension["ColumbaNetworkExtension"]
LXSTSwift["LXSTSwift"]
MapLibre["MapLibre"]
RNSAPI["RNSAPI"]
SwiftBLEBridge["SwiftBLEBridge"]
ColumbaApp --> LXSTSwift
ColumbaApp --> MapLibre
ColumbaApp --> RNSAPI
ColumbaApp --> SwiftBLEBridge
LXSTSwift --> CCodec2
LXSTSwift --> COpus
LXSTSwift --> RNSAPI
SwiftBLEBridge --> RNSAPI
classDef app fill:#1f6feb,stroke:#0d419d,color:#fff
classDef extension fill:#8957e5,stroke:#553098,color:#fff
classDef bridge fill:#f0883e,stroke:#9e4c0f,color:#fff
classDef spm_lib fill:#3fb950,stroke:#0f7a2e,color:#fff
classDef c_lib fill:#6e7681,stroke:#30363d,color:#fff
class ColumbaApp app
class LXSTSwift,MapLibre,RNSAPI,SwiftBLEBridge spm_lib
class ColumbaNetworkExtension extension
class CCodec2,COpus c_lib
```
<!-- module-graph-end -->
875 changes: 697 additions & 178 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.

89 changes: 89 additions & 0 deletions Columba.xcodeproj/xcshareddata/xcschemes/Columba-Swift.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TARG"
BuildableName = "ColumbaApp.app"
BlueprintName = "ColumbaApp"
ReferencedContainer = "container:Columba.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug-Swift"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TTARG"
BuildableName = "ColumbaAppTests.xctest"
BlueprintName = "ColumbaAppTests"
ReferencedContainer = "container:Columba.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug-Swift"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TARG"
BuildableName = "ColumbaApp.app"
BlueprintName = "ColumbaApp"
ReferencedContainer = "container:Columba.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release-Swift"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "TARG"
BuildableName = "ColumbaApp.app"
BlueprintName = "ColumbaApp"
ReferencedContainer = "container:Columba.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug-Swift">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release-Swift"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
4 changes: 4 additions & 0 deletions Frameworks/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Python.xcframework lives in this directory. Fetch it with `support/fetch-python.sh`.
The xcframework, the VERSIONS marker, and the upstream tarball are all gitignored
because the xcframework is ~110 MB. The fetch script pins a specific release tag
for reproducibility — see support/fetch-python.sh.
81 changes: 52 additions & 29 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,45 +1,68 @@
// swift-tools-version: 5.9
import PackageDescription

// Module layout — the iOS app is Xcode-built (Columba.xcodeproj), with
// configure-xcodeproj.rb pulling files in by path. This SwiftPM manifest
// exists for two reasons:
//
// 1. The Xcode project references this manifest as a LOCAL package
// (XCLocalSwiftPackageReference) so RNSAPI / SwiftBLEBridge get built by
// SwiftPM rather than hand-written pbxproj entries.
// 2. `swift build` (used by tooling + CI) can still typecheck the pure-Swift
// libraries without the Python.xcframework bridging header.
//
// The LXST voice stack (LXSTSwift + the Opus/Codec2 codec C trees) is no longer
// vendored here — it lives in the standalone, transport-agnostic LXST-swift
// package (consumed via SwiftPM, wired to RNS through Columba's
// PythonNetworkTransport). See `dependencies` below.
//
// Targets that DO require the bridging header (PythonBridge, RNSBackendPy,
// ColumbaApp) live ONLY in the pbxproj — they're not declared here.
let package = Package(
name: "ColumbaApp",
platforms: [
.iOS(.v17),
.macOS(.v14)
],
products: [
.executable(
name: "ColumbaApp",
targets: ["ColumbaApp"]
)
.library(name: "RNSAPI", targets: ["RNSAPI"]),
.library(name: "SwiftBLEBridge", targets: ["SwiftBLEBridge"]),
],
dependencies: [
// SPM resolves these from GitHub on every fresh checkout. To work
// against an in-progress local clone of any of these libraries
// without committing a path-override, drop a per-machine
// `.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.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.3.0"),
.package(url: "https://github.com/maplibre/maplibre-gl-native-distribution", from: "6.9.0"),
// Transport-agnostic LXST voice library (owns the Opus/Codec2 codecs
// and the NetworkTransport seam; no Reticulum dependency). Columba
// provides the implementation via PythonNetworkTransport. Tracking the
// branch until a release is tagged — same model as the RNS fork.
.package(url: "https://github.com/torlando-tech/LXST-swift.git", branch: "feat/transport-agnostic"),
],
targets: [
.executableTarget(
name: "ColumbaApp",
dependencies: [
.product(name: "LXMFSwift", package: "LXMF-swift"),
.product(name: "LXSTSwift", package: "LXST-swift"),
// ReticulumSwift is imported directly by several view
// models (e.g. NomadNetBrowserViewModel,
// MessagingViewModel) — listed here as a direct product
// dep so the version constraint on the package above is
// actually exercised by SPM at resolution time.
.product(name: "ReticulumSwift", package: "reticulum-swift"),
.product(name: "MapLibre", package: "maplibre-gl-native-distribution"),
],
path: "Sources/ColumbaApp"
)
// ──────── RNSAPI: pure-interface protocol surface ────────
.target(
name: "RNSAPI",
path: "Sources/RNSAPI",
// libsqlite3 (system) backs LXMFDatabase's on-disk persistence.
linkerSettings: [.linkedLibrary("sqlite3")]
),

// ──────── SwiftBLEBridge: CoreBluetooth wrapper for ble-reticulum ──
// Mirror of Columba Android's reticulum/ble module. Holds CBCentralManager
// + CBPeripheralManager state and exposes a Swift API that the iOS BLE
// driver (app/ble/ios_ble_driver.py) calls into. The Python ↔ Swift
// callback invocation path lives separately in the pbxproj-only
// `PythonBLECallbackBridge.swift` (which needs Python.h); SwiftBLEBridge
// itself is pure CoreBluetooth so `swift build` compiles it cleanly.
.target(
name: "SwiftBLEBridge",
dependencies: ["RNSAPI"],
path: "Sources/SwiftBLEBridge"
),
// Pure-Swift unit tests for RNSAPI (msgpack, AppDataParser,
// PropagationNodeInfo). Runs natively via `swift test` on macOS — no
// simulator / Xcode test target needed (RNSAPI has no UIKit/Python deps).
.testTarget(
name: "RNSAPITests",
dependencies: ["RNSAPI"],
path: "Tests/RNSAPITests"
),
]
)
35 changes: 35 additions & 0 deletions Sources/PythonBridge/ColumbaPython-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef ColumbaPython_Bridging_Header_h
#define ColumbaPython_Bridging_Header_h

#import <Python/Python.h>

// Swift cannot express `PyConfig_SetString(&config, &config.home, ...)` because of
// overlapping-access exclusivity rules — both arguments alias into `config`.
// These tiny inline shims pull the dual-mutation pattern down into C, which
// has no such restriction. One shim per `PyConfig` field we touch.

static inline PyStatus ColumbaPy_PyConfig_SetHome(PyConfig *config, const wchar_t *home) {
return PyConfig_SetString(config, &config->home, home);
}

// Py_None is a macro in CPython's headers, which Swift cannot import. This
// shim returns a fresh reference so the caller can pass it where a "new"
// reference is expected (e.g. PyTuple_SetItem, which steals refs).
static inline PyObject *ColumbaPy_None(void) {
Py_INCREF(Py_None);
return Py_None;
}

// Py_True / Py_False are likewise macros. New refs so they can be packed into
// tuples + lists where slot setters steal the ref.
static inline PyObject *ColumbaPy_True(void) {
Py_INCREF(Py_True);
return Py_True;
}

static inline PyObject *ColumbaPy_False(void) {
Py_INCREF(Py_False);
return Py_False;
}

#endif
Loading
Loading