Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 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
50 changes: 45 additions & 5 deletions Columba.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
T001 /* AudioRingBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT01 /* AudioRingBufferTests.swift */; };
T002 /* AudioManagerConfigChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT02 /* AudioManagerConfigChangeTests.swift */; };
T004 /* CallManagerCallKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT04 /* CallManagerCallKitTests.swift */; };
T005 /* MapStyleURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT05 /* MapStyleURLTests.swift */; };
001 /* ColumbaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F001 /* ColumbaApp.swift */; };
002 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F002 /* Theme.swift */; };
003 /* ChatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F003 /* ChatsViewModel.swift */; };
Expand Down Expand Up @@ -49,8 +50,12 @@
037 /* ProfileIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F037 /* ProfileIcon.swift */; };
038 /* IconPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F038 /* IconPickerView.swift */; };
039 /* materialdesignicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F039 /* materialdesignicons.ttf */; };
FNT1 /* JetBrainsMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FNT1F /* JetBrainsMono-Regular.ttf */; };
FNT2 /* JetBrainsMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FNT2F /* JetBrainsMono-Bold.ttf */; };
040 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F040 /* NotificationService.swift */; };
041 /* AutoAnnounceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F041 /* AutoAnnounceManager.swift */; };
041P /* AutoAnnouncePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F041P /* AutoAnnouncePolicy.swift */; };
041R /* PeerChildInterfaceRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F041R /* PeerChildInterfaceRegistry.swift */; };
042 /* LocalIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = F042 /* LocalIdentity.swift */; };
043 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F043 /* IdentityManager.swift */; };
044 /* IdentityManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F044 /* IdentityManagerView.swift */; };
Expand Down Expand Up @@ -118,8 +123,13 @@
082B /* MicronRenderContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F083 /* MicronRenderContainer.swift */; };
083B /* MonospaceLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F084 /* MonospaceLineView.swift */; };
084B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F085 /* ZoomableScrollView.swift */; };
085B /* BackgroundTransportPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F086 /* BackgroundTransportPage.swift */; };
085B /* BackgroundTransportPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F088 /* BackgroundTransportPage.swift */; };
086B /* TCPClientWizardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F086 /* TCPClientWizardViewModel.swift */; };
087B /* TCPClientWizard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F087 /* TCPClientWizard.swift */; };
T006 /* TCPClientWizardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT06 /* TCPClientWizardViewModelTests.swift */; };
T003 /* MicronParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FT03 /* MicronParserTests.swift */; };
TAA0 /* AutoAnnouncePolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FTAA /* AutoAnnouncePolicyTests.swift */; };
TPCR /* PeerChildInterfaceRegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FTPC /* PeerChildInterfaceRegistryTests.swift */; };
2F3D64B12F7227E100049252 /* ReticulumSwift in Frameworks */ = {isa = PBXBuildFile; productRef = P004 /* ReticulumSwift */; };
ERETIC /* ReticulumSwift in Frameworks */ = {isa = PBXBuildFile; productRef = P005 /* ReticulumSwift */; };
E001 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE01 /* PacketTunnelProvider.swift */; };
Expand Down Expand Up @@ -164,6 +174,9 @@
FT02 /* AudioManagerConfigChangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManagerConfigChangeTests.swift; sourceTree = "<group>"; };
FT04 /* CallManagerCallKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManagerCallKitTests.swift; sourceTree = "<group>"; };
FT03 /* MicronParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicronParserTests.swift; sourceTree = "<group>"; };
FTAA /* AutoAnnouncePolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoAnnouncePolicyTests.swift; sourceTree = "<group>"; };
FTPC /* PeerChildInterfaceRegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerChildInterfaceRegistryTests.swift; sourceTree = "<group>"; };
FT05 /* MapStyleURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStyleURLTests.swift; sourceTree = "<group>"; };
TPROD /* ColumbaAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ColumbaAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EPROD /* ColumbaNetworkExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ColumbaNetworkExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F001 /* ColumbaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumbaApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -205,8 +218,12 @@
F037 /* ProfileIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileIcon.swift; sourceTree = "<group>"; };
F038 /* IconPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPickerView.swift; sourceTree = "<group>"; };
F039 /* materialdesignicons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = materialdesignicons.ttf; sourceTree = "<group>"; };
FNT1F /* JetBrainsMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Regular.ttf"; sourceTree = "<group>"; };
FNT2F /* JetBrainsMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Bold.ttf"; sourceTree = "<group>"; };
F040 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
F041 /* AutoAnnounceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoAnnounceManager.swift; sourceTree = "<group>"; };
F041P /* AutoAnnouncePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoAnnouncePolicy.swift; sourceTree = "<group>"; };
F041R /* PeerChildInterfaceRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerChildInterfaceRegistry.swift; sourceTree = "<group>"; };
F042 /* LocalIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalIdentity.swift; sourceTree = "<group>"; };
F043 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; };
F044 /* IdentityManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManagerView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -273,7 +290,10 @@
F083 /* MicronRenderContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicronRenderContainer.swift; sourceTree = "<group>"; };
F084 /* MonospaceLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonospaceLineView.swift; sourceTree = "<group>"; };
F085 /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
F086 /* BackgroundTransportPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTransportPage.swift; sourceTree = "<group>"; };
F088 /* BackgroundTransportPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTransportPage.swift; sourceTree = "<group>"; };
F086 /* TCPClientWizardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPClientWizardViewModel.swift; sourceTree = "<group>"; };
F087 /* TCPClientWizard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPClientWizard.swift; sourceTree = "<group>"; };
FT06 /* TCPClientWizardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPClientWizardViewModelTests.swift; sourceTree = "<group>"; };
F07B /* Config/Signing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config/Signing.xcconfig; sourceTree = SOURCE_ROOT; };
F07C /* Config/LocalSigning.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config/LocalSigning.xcconfig.example; sourceTree = SOURCE_ROOT; };
FE01 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -421,7 +441,7 @@
F04F /* ConnectivityPage.swift */,
F050 /* PermissionsPage.swift */,
F051 /* CompletePage.swift */,
F086 /* BackgroundTransportPage.swift */,
F088 /* BackgroundTransportPage.swift */,
F079 /* OnboardingRestoreSheet.swift */,
);
path = Onboarding;
Expand All @@ -445,6 +465,8 @@
F022 /* Assets.xcassets */,
F023 /* Info.plist */,
F039 /* materialdesignicons.ttf */,
FNT1F /* JetBrainsMono-Regular.ttf */,
FNT2F /* JetBrainsMono-Bold.ttf */,
F075 /* ColumbaApp.entitlements */,
);
path = Resources;
Expand Down Expand Up @@ -480,6 +502,7 @@
F066 /* AppearanceCard.swift */,
F067 /* CustomThemeEditorView.swift */,
F071 /* BLEConnectionsView.swift */,
F087 /* TCPClientWizard.swift */,
GRNW /* RNodeWizard */,
);
path = Settings;
Expand All @@ -505,6 +528,8 @@
F033 /* PropagationNodeManager.swift */,
F040 /* NotificationService.swift */,
F041 /* AutoAnnounceManager.swift */,
F041P /* AutoAnnouncePolicy.swift */,
F041R /* PeerChildInterfaceRegistry.swift */,
F042 /* LocalIdentity.swift */,
F043 /* IdentityManager.swift */,
F04B /* LocationSharingManager.swift */,
Expand Down Expand Up @@ -564,6 +589,7 @@
F05A /* RNodeWizardViewModel.swift */,
F05F /* MigrationViewModel.swift */,
F080 /* NomadNetBrowserViewModel.swift */,
F086 /* TCPClientWizardViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand All @@ -585,6 +611,10 @@
FT02 /* AudioManagerConfigChangeTests.swift */,
FT03 /* MicronParserTests.swift */,
FT04 /* CallManagerCallKitTests.swift */,
FTAA /* AutoAnnouncePolicyTests.swift */,
FTPC /* PeerChildInterfaceRegistryTests.swift */,
FT05 /* MapStyleURLTests.swift */,
FT06 /* TCPClientWizardViewModelTests.swift */,
);
path = Tests/ColumbaAppTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -735,6 +765,8 @@
files = (
022 /* Assets.xcassets in Resources */,
039 /* materialdesignicons.ttf in Resources */,
FNT1 /* JetBrainsMono-Regular.ttf in Resources */,
FNT2 /* JetBrainsMono-Bold.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -762,6 +794,10 @@
T002 /* AudioManagerConfigChangeTests.swift in Sources */,
T003 /* MicronParserTests.swift in Sources */,
T004 /* CallManagerCallKitTests.swift in Sources */,
TAA0 /* AutoAnnouncePolicyTests.swift in Sources */,
TPCR /* PeerChildInterfaceRegistryTests.swift in Sources */,
T005 /* MapStyleURLTests.swift in Sources */,
T006 /* TCPClientWizardViewModelTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -819,6 +855,8 @@
038 /* IconPickerView.swift in Sources */,
040 /* NotificationService.swift in Sources */,
041 /* AutoAnnounceManager.swift in Sources */,
041P /* AutoAnnouncePolicy.swift in Sources */,
041R /* PeerChildInterfaceRegistry.swift in Sources */,
042 /* LocalIdentity.swift in Sources */,
043 /* IdentityManager.swift in Sources */,
044 /* IdentityManagerView.swift in Sources */,
Expand Down Expand Up @@ -883,6 +921,8 @@
082B /* MicronRenderContainer.swift in Sources */,
083B /* MonospaceLineView.swift in Sources */,
084B /* ZoomableScrollView.swift in Sources */,
086B /* TCPClientWizardViewModel.swift in Sources */,
087B /* TCPClientWizard.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1229,7 +1269,7 @@
repositoryURL = "https://github.com/torlando-tech/LXMF-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.3.4;
minimumVersion = 0.4.0;
};
};
PKGREF2 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */ = {
Expand All @@ -1253,7 +1293,7 @@
repositoryURL = "https://github.com/torlando-tech/reticulum-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.2.3;
minimumVersion = 0.3.0;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down

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
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
Loading