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.
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/optionson 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):
Multi-bridge (new):
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).Implementation
Pattern for every per-bridge view:
AppEnvironmentgains: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 onenvironment.registry.sessions.count, render the Bridges section in multi-bridge mode.Files
New:
Shellbee/Features/Settings/BridgeSettingsView.swiftEdited:
Shellbee/App/AppEnvironment.swift— add per-bridgesendBridgeOptions(_:to:)+restartBridge(_:).Shellbee/Features/Settings/SettingsView.swift— multi-bridge branch.bridgeID: UUID? = nil, threadsstore/sendthrough it.Verification
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_subon each broker, or App Insights/test-center).Phase
Phase 2 multi-bridge polish. Closes the per-screen settings scope of #71 / #72.