Skip to content

Refactor runtime and store architecture#1061

Draft
jackjackbits wants to merge 10 commits intomainfrom
codex/runtime-store-refactor
Draft

Refactor runtime and store architecture#1061
jackjackbits wants to merge 10 commits intomainfrom
codex/runtime-store-refactor

Conversation

@jackjackbits
Copy link
Copy Markdown
Collaborator

@jackjackbits jackjackbits commented Mar 14, 2026

Summary

  • introduce AppRuntime as the lifecycle owner for transport startup, foreground/background transitions, and shared live services
  • split app state into feature stores for public timeline, private conversations, session state, peer presentation, verification, geohash people, and composer state
  • reduce ChatViewModel from a god object into a compatibility and action layer, with transport ingress and reconciliation moved behind runtime/store seams
  • remove mutable message-in-collection patterns, singleton fallbacks, and NotificationCenter-driven fanout in the command, favorites, relay, and network paths

Automated Testing

  • xcodebuild -project bitchat.xcodeproj -scheme "bitchat (macOS)" -configuration Debug CODE_SIGNING_ALLOWED=NO build
  • xcodebuild -project bitchat.xcodeproj -scheme "bitchat (iOS)" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' -parallel-testing-enabled NO test -only-testing:bitchatTests_iOS/CommandProcessorTests -only-testing:bitchatTests_iOS/NetworkActivationServiceTests -only-testing:bitchatTests_iOS/NostrRelayManagerTests -only-testing:bitchatTests_iOS/ViewSmokeTests -only-testing:bitchatTests_iOS/AppRuntimeTests
  • xcodebuild -project bitchat.xcodeproj -scheme "bitchat (iOS)" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' test

Manual Testing Plan

  • Launch the app from a clean start and verify the main chat screen loads, the nickname/session state is intact, and the people sheet opens without stale state or duplicate alerts
  • Send a public message in the default timeline, switch channels, send another message, then return to the original channel and verify timeline contents and composer/autocomplete state stay consistent
  • Put the app in the background and bring it back to the foreground, then confirm transport state, relay state, and session banners recover cleanly without duplicate reconnect side effects
  • Open a private conversation from the people list, send and receive messages, and verify unread counts, delivery status, and conversation selection update correctly
  • Remove a private conversation, then trigger the panic-clear/reset flow and confirm public timeline state, private conversation state, and relay/channel state all reset cleanly
  • Open the fingerprint / verification UI for a peer and verify the sheet, QR/fingerprint presentation, and follow-up verification state transitions still work
  • Join a geohash/location channel and verify the people list, nickname display, command suggestions, and GeoDM actions resolve the expected participants
  • Add and remove a favorite channel or geohash and confirm the favorite state updates immediately across the affected UI without requiring a relaunch

Manual testing is best on a real-device pairing or a real device plus macOS build for BLE and peer-to-peer flows. Simulator coverage is still useful for screen state, command handling, and Nostr/geohash flows.

Risk Areas

  • runtime lifecycle and transport ingress changes
  • private-conversation reconciliation and delivery status updates
  • Nostr and geohash relay flow, including Tor activation gating
  • SwiftUI screen decomposition and store observation behavior

@jackjackbits jackjackbits marked this pull request as draft March 14, 2026 01:39
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 333ee7413e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bitchat/Architecture/TransportRuntimeController.swift Outdated
@qalandarov
Copy link
Copy Markdown
Collaborator

this is what claude is saying:

  1. PrivateConversationsStore — missing @MainActor on mutating methods

  PrivateChatManager.swift:14 — The class has @Published properties (privateChats, selectedPeer, unreadMessages) but is NOT @MainActor. Three methods mutate these properties without main actor isolation:

  - migrateSelectedPeerOnDisconnect (line 533) — mutates selectedPeer
  - clearSentReadReceipts (line 549) — mutates sentReadReceipts + reads privateChats
  - cleanupStaleUnreadPeerIDs (line 561) — mutates unreadMessages

  These are called from TransportRuntimeController (which IS @MainActor), so they get implicitly hopped to main. But the inconsistency means the compiler can't enforce this — and any future call site outside a @MainActor context will silently race. The
  entire class should either be @MainActor or these methods need the annotation.

  ---
  2. TransportEventBridge.emit() — event ordering not guaranteed

  TransportEventBridge.swift:58-62

  func emit(_ event: TransportEvent) {
      Task {
          await transportCore.emit(event)
      }
  }

  BLE delegate callbacks fire synchronously in a deterministic order, but each emit() creates an unstructured Task that races to reach the BLETransportCore actor. The actor serializes execution, but task scheduling order is non-deterministic. A
  .messageReceived and .messageDeliveryStatusUpdated for the same message could swap. The didUpdatePeerSnapshots delegate method is @MainActor while others aren't, worsening this.

  ---
  3. default: in switch statements — violates CLAUDE.md

  Two places in new code use default: which the project prohibits (since it silently ignores future enum cases):

  - VerificationStore.swift:99 — default: return in handleVerificationPayload. New NoisePayloadType cases will be silently dropped.
  - TransportRuntimeController.swift:329 — default: return false in shouldSkipUpdate. New DeliveryStatus cases won't be protected from downgrading.

  (@unknown default in SessionStore.swift:80 is fine — required for non-frozen CBManagerState.)

  ---
  4. AppRuntime.checkForSharedContent() — DispatchQueue.main.async inside @MainActor

  AppRuntime.swift:181

  DispatchQueue.main.async { [chatViewModel] in
      chatViewModel.sendMessage(...)
  }

  AppRuntime is @MainActor, so this is already on the main thread. The DispatchQueue.main.async just defers to the next run loop iteration unnecessarily. Should be a direct call.

  ---
  5. BLETransportCore.pendingEvents — unbounded buffer

  BLETransportCore.swift:29

  Events emitted before the first subscriber are buffered in pendingEvents with no size cap. During slow startup (Tor, BLE init), this array can grow indefinitely.

  ---
  6. TransportRuntimeController — weak viewModel silently drops events

  TransportRuntimeController.swift — ~15 guard sites

  Every handler does guard let viewModel else { return } with no logging. If ChatViewModel is deallocated prematurely (lifecycle bug), transport events are silently discarded with no indication anything is wrong. At minimum the guards should log at debug
  level.

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.

2 participants