Skip to content

Multi-bridge: per-bridge Settings pages #81

@tashda

Description

@tashda

Why

Most Z2M settings are inherently per-bridge — last_seen, cache_state, MQTT broker, adapter port, network channel/pan, log level, OTA throttle, HomeAssistant integration, availability timeouts, blocklist/passlist, frontend port, backup, touchlink. With Phase 2 multi-bridge shipped, the current Settings UI silently routes every setting to whichever bridge is focused. That's a leaky abstraction: users with two bridges configuring them independently can't tell which one they're editing.

The user-facing fix: model Settings the way Z2M itself models bridges — each instance has its own config page.

Audit (which screens are per-bridge)

Per-bridge (write to bridge/request/options on a specific bridge or operate on a single bridge's state):
MainSettingsView (General), MQTTSettingsView, SerialSettingsView, NetworkSettingsView, NetworkAccessSettingsView, LogOutputView, HealthSettingsView, HomeAssistantSettingsView, AvailabilitySettingsView, OTASettingsView, FrontendSettingsView, ServerDetailView, Backup/BackupView, TouchlinkView, the inline Logging Level picker, and the Restart / Disconnect actions.

App-global (live in iOS prefs, never touch Z2M):
AppGeneralView, AppLiveActivitiesView, AppNotificationSettingsView, AboutView, SavedBridgesView, DeveloperSettings, DocBrowser*, DeviceStatistics.

Plan

Two-tier Settings, branching on connected-bridge count:

Single-bridge (current behavior, unchanged):

  • App-level sections + bridge-scoped sections all on the same page, implicitly targeting the one bridge.

Multi-bridge (new):

  • App section (General / Live Activities / Notifications / About) — global.
  • Connection section (Saved Bridges).
  • Bridges section — one row per saved bridge with color dot, name, and live connection state. Tap → BridgeSettingsView(bridgeID:) for that bridge, showing only its per-bridge sections (Server, General, MQTT, Adapter, Logging, Logging Output, HA, Availability, OTA, Health, Network & Hardware, Device Filtering, Touchlink, Backup, Disconnect).
  • Tools (Device Library) — global.
  • Logs viewer — global (logs already merge across bridges).

Implementation

Pattern for every per-bridge view:

struct MQTTSettingsView: View {
    var bridgeID: UUID? = nil  // nil → focused bridge (single-bridge fallback)

    private var session: BridgeSession? {
        bridgeID.flatMap { environment.registry.session(for: $0) }
    }
    private var store: AppStore { session?.store ?? environment.store }
    private var bridgeInfo: BridgeInfo? { store.bridgeInfo }

    private func sendOptions(_ options: [String: JSONValue]) {
        if let bridgeID {
            environment.sendBridgeOptions(options, to: bridgeID)
        } else {
            environment.sendBridgeOptions(options)
        }
    }
}

AppEnvironment gains:

  • sendBridgeOptions(_:to bridgeID:) — explicit-bridge variant of the existing wrapper.
  • restartBridge(_:) — explicit bridge.

New file: Shellbee/Features/Settings/BridgeSettingsView.swift — hosts the per-bridge sections with the bridge's color, status header, and restart-required banner.

Modified: SettingsView.swift — branch on environment.registry.sessions.count, render the Bridges section in multi-bridge mode.

Files

New:

  • Shellbee/Features/Settings/BridgeSettingsView.swift

Edited:

  • Shellbee/App/AppEnvironment.swift — add per-bridge sendBridgeOptions(_:to:) + restartBridge(_:).
  • Shellbee/Features/Settings/SettingsView.swift — multi-bridge branch.
  • Per-bridge child views (16): each gains bridgeID: UUID? = nil, threads store/send through it.

Verification

  • Manual: docker compose up, save both bridges, connect both. Open Settings → see "Bridges" section. Tap each bridge → its config page. Change MQTT throttle on one bridge — confirm the other's MQTT is untouched (mosquitto_sub on each broker, or App Insights/test-center).
  • Single-bridge: delete one saved bridge, confirm Settings collapses to the legacy flat layout.
  • All existing unit tests still pass.

Phase

Phase 2 multi-bridge polish. Closes the per-screen settings scope of #71 / #72.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions