From 5ca467f39978f642d1b100d3b88215304475cd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:13:10 +0200 Subject: [PATCH 1/2] Restore last used server and path on cold launch The SwiftUI lifecycle migration dropped scene state restoration, so the app always reopened on the first server at its default page. Persist the last-active server id and a host-agnostic path in SettingsStore and restore them at launch, rebuilding the path against the currently active base URL. --- Sources/App/Container/ContainerView.swift | 8 +- .../Container/OnboardingStateObservable.swift | 45 ++++++++--- .../App/Frontend/WebView/FrontendView.swift | 10 +-- .../HomeAssistantView/HomeAssistantView.swift | 6 +- .../HomeAssistantViewModel.swift | 7 +- .../WebViewController+URLLoading.swift | 22 +++++- .../WebViewController+WebViewSetup.swift | 14 ++++ .../WebViewController/WebViewController.swift | 7 +- Sources/Shared/Settings/SettingsStore.swift | 18 +++++ .../OnboardingStateObservableTests.swift | 77 +++++++++++++++++++ .../App/WebView/WebViewControllerTests.swift | 37 ++++++++- 11 files changed, 218 insertions(+), 33 deletions(-) create mode 100644 Tests/App/Container/OnboardingStateObservableTests.swift diff --git a/Sources/App/Container/ContainerView.swift b/Sources/App/Container/ContainerView.swift index 78974b6886..0e19ecd952 100644 --- a/Sources/App/Container/ContainerView.swift +++ b/Sources/App/Container/ContainerView.swift @@ -26,8 +26,8 @@ struct ContainerView: View { case let .onboarding(style): OnboardingHostingView(onboardingStyle: style) .id(style) - case let .webView(server): - HomeAssistantView(server: server) { webViewController in + case let .webView(server, initialPath): + HomeAssistantView(server: server, initialPath: initialPath) { webViewController in coordinator.setFrontend(webViewController) Current.sceneManager.setWebViewController(webViewController) } @@ -115,7 +115,9 @@ struct ContainerView: View { } private var isShowingWebView: Bool { - if case .webView = state.screen { return true } + if case .webView = state.screen { + return true + } return false } } diff --git a/Sources/App/Container/OnboardingStateObservable.swift b/Sources/App/Container/OnboardingStateObservable.swift index 3a0474f2a1..0578313a3d 100644 --- a/Sources/App/Container/OnboardingStateObservable.swift +++ b/Sources/App/Container/OnboardingStateObservable.swift @@ -23,7 +23,9 @@ enum RecoveredServerReauthenticationError: LocalizedError { final class OnboardingStateObservable: ObservableObject { enum Screen: Equatable { case onboarding(OnboardingStyle) - case webView(Server) + /// `initialPath` restores the last viewed path on cold launch only; in-session server switches + /// pass `nil` so switching never yanks the web view back to the launch path. + case webView(Server, initialPath: String?) case recoveredServerImport case recoveredServerReauth(Server) } @@ -40,7 +42,7 @@ final class OnboardingStateObservable: ObservableObject { /// Switches the displayed screen to `server`'s web view. Called by the app coordinator's `open(server:)`. func showWebView(for server: Server) { - screen = .webView(server) + screen = .webView(server, initialPath: nil) } /// A change to the kiosk server requires a different web view (rebuilt by `ContainerView` via the @@ -77,7 +79,9 @@ final class OnboardingStateObservable: ObservableObject { } private var currentServer: Server? { - if case let .webView(server) = screen { return server } + if case let .webView(server, _) = screen { + return server + } return nil } @@ -163,8 +167,8 @@ final class OnboardingStateObservable: ObservableObject { } } - /// Mirrors `WebViewWindowController.setup()`: onboarding when required, otherwise the first server's - /// web view. + /// Mirrors `WebViewWindowController.setup()`: onboarding when required, otherwise the last viewed + /// server's web view (restoring its last path). private static func initialScreen() -> Screen { if Current.servers.isMirrorRestorePending { return .recoveredServerImport @@ -176,21 +180,38 @@ final class OnboardingStateObservable: ObservableObject { return .onboarding(style) } if let server = preferredInitialServer() { - return .webView(server) + return .webView(server, initialPath: restoredInitialPath(for: server)) } return .onboarding(.initial) } /// The server to show at launch: the kiosk-configured server when kiosk mode is enabled, otherwise the - /// first registered server. - private static func preferredInitialServer() -> Server? { + /// last server the user was viewing, falling back to the first registered server. Non-private for tests. + static func preferredInitialServer() -> Server? { let kiosk = Current.kioskSettings if kiosk.enabled, let server = Current.servers.server(forServerIdentifier: kiosk.serverId) { return server } + if let identifier = Current.settingsStore.lastActiveServerIdentifier, + let server = Current.servers.server(forServerIdentifier: identifier) { + return server + } return Current.servers.all.first } + /// The path to restore into the launch web view, or `nil` to load the server default. Gated by the + /// "Remember Last Page" setting and only applied when the launch server is the one that was persisted, + /// so a fallback to the first server (e.g. after the saved server was removed) never inherits a stale + /// path. Only the path is stored; the base URL is re-resolved at load time from current connectivity. + /// Non-private for tests. + static func restoredInitialPath(for server: Server) -> String? { + guard Current.settingsStore.restoreLastURL, + Current.settingsStore.lastActiveServerIdentifier == server.identifier.rawValue else { + return nil + } + return Current.settingsStore.lastActiveURLPath + } + /// The server shown at launch (preferring one that doesn't need re-auth), if it requires re-auth after a /// keychain-mirror restore. Mirrors `WebViewWindowController.nextRecoveredServerNeedingReauthentication`. private static func recoveredServerNeedingReauthentication() -> Server? { @@ -208,7 +229,7 @@ final class OnboardingStateObservable: ObservableObject { // A server was removed / logged out. Fall back to another server if one remains, // otherwise restart onboarding. Mirrors `WebViewWindowController.onboardingStateDidChange`. if let server = Current.servers.all.first { - screen = .webView(server) + screen = .webView(server, initialPath: nil) } else { screen = .onboarding(.initial) } @@ -219,7 +240,7 @@ final class OnboardingStateObservable: ObservableObject { } case .complete: if let server = Current.servers.all.first { - screen = .webView(server) + screen = .webView(server, initialPath: nil) } case .didConnect: // Connection established mid-onboarding; the `.complete` transition drives the screen swap. @@ -229,8 +250,8 @@ final class OnboardingStateObservable: ObservableObject { } extension OnboardingStateObservable: OnboardingStateObserver { - // `OnboardingStateObservation` may notify from a non-main thread, so hop to the main actor before - // mutating the published `screen`. + /// `OnboardingStateObservation` may notify from a non-main thread, so hop to the main actor before + /// mutating the published `screen`. nonisolated func onboardingStateDidChange(to state: OnboardingState) { Task { @MainActor [weak self] in self?.apply(state) diff --git a/Sources/App/Frontend/WebView/FrontendView.swift b/Sources/App/Frontend/WebView/FrontendView.swift index f91e310346..cbc87e9342 100644 --- a/Sources/App/Frontend/WebView/FrontendView.swift +++ b/Sources/App/Frontend/WebView/FrontendView.swift @@ -6,7 +6,7 @@ import UIKit /// fresh controller and discards the previous one. struct FrontendView: UIViewControllerRepresentable { let server: Server - var restorationType: WebViewRestorationType? + var initialPath: String? var onWebViewController: ((WebViewController) -> Void)? var onWebViewLoaded: ((WebViewController) -> Void)? var resetFrontendAction: (() -> Void)? @@ -15,7 +15,7 @@ struct FrontendView: UIViewControllerRepresentable { init( server: Server, - restorationType: WebViewRestorationType? = nil, + initialPath: String? = nil, onWebViewController: ((WebViewController) -> Void)? = nil, onWebViewLoaded: ((WebViewController) -> Void)? = nil, resetFrontendAction: (() -> Void)? = nil, @@ -23,7 +23,7 @@ struct FrontendView: UIViewControllerRepresentable { overlayState: WebFrontendOverlayState ) { self.server = server - self.restorationType = restorationType + self.initialPath = initialPath self.onWebViewController = onWebViewController self.onWebViewLoaded = onWebViewLoaded self.resetFrontendAction = resetFrontendAction @@ -41,10 +41,10 @@ struct FrontendView: UIViewControllerRepresentable { // No-op: a server change recreates this view (keyed by server in `ContainerView`), never updates it. } - // Non-private for tests. + /// Non-private for tests. func makeWebViewController() -> WebViewController { let controller = WebViewController(server: server) - controller.initialURL = restorationType?.initialURL + controller.initialURLPath = initialPath controller.overlayState = overlayState controller.resetFrontendAction = resetFrontendAction controller.reconnectManager = reconnectManager diff --git a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift index cbbafebfc3..d3cc698ab0 100644 --- a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift +++ b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift @@ -7,16 +7,17 @@ struct HomeAssistantView: View, WebFrontendView { @Environment(\.accessibilityReduceMotion) private var reduceMotion - init(server: Server, onWebViewController: @escaping (WebViewController) -> Void) { + init(server: Server, initialPath: String? = nil, onWebViewController: @escaping (WebViewController) -> Void) { _viewModel = StateObject( wrappedValue: HomeAssistantViewModel( server: server, + initialPath: initialPath, onWebViewController: onWebViewController ) ) } - // The themed status-bar strip keeps the last frontend-provided colour until WebKit sends a new update. + /// The themed status-bar strip keeps the last frontend-provided colour until WebKit sends a new update. private var themedStatusBar: some View { GeometryReader { proxy in if let color = viewModel.overlayState.statusBarColor { @@ -59,6 +60,7 @@ struct HomeAssistantView: View, WebFrontendView { private var homeAssistant: some View { FrontendView( server: viewModel.server, + initialPath: viewModel.initialPath, onWebViewController: viewModel.handleWebViewController, onWebViewLoaded: viewModel.handleWebViewLoaded, resetFrontendAction: viewModel.resetWebFrontend, diff --git a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantViewModel.swift b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantViewModel.swift index de3b87ac86..27607b0d61 100644 --- a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantViewModel.swift +++ b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantViewModel.swift @@ -19,6 +19,7 @@ final class HomeAssistantViewModel: ObservableObject { } let server: Server + let initialPath: String? let overlayState: WebFrontendOverlayState let chrome: WebViewChromeState let reconnectManager: WebViewReconnectManager @@ -49,12 +50,14 @@ final class HomeAssistantViewModel: ObservableObject { init( server: Server, + initialPath: String? = nil, overlayState: WebFrontendOverlayState? = nil, chrome: WebViewChromeState? = nil, reconnectManager: WebViewReconnectManager? = nil, onWebViewController: ((WebViewController) -> Void)? = nil ) { self.server = server + self.initialPath = initialPath self.overlayState = overlayState ?? WebFrontendOverlayState() self.chrome = chrome ?? WebViewChromeState() self.reconnectManager = reconnectManager ?? WebViewReconnectManager() @@ -100,8 +103,8 @@ final class HomeAssistantViewModel: ObservableObject { overlayState.emptyState == nil && !isFullScreenLoaderVisible ? 0 : 1 } - // On servers that support `frontend/loaded`, the first bootstrap must wait for that event (the frontend's - // own launcher screen is still up on plain `connected`). + /// On servers that support `frontend/loaded`, the first bootstrap must wait for that event (the frontend's + /// own launcher screen is still up on plain `connected`). private func didReachLoaderReadyState(_ connectionState: FrontEndConnectionState) -> Bool { guard server.info.version >= .frontendLoadedExternalBus else { return connectionState.isReadyForDisplay diff --git a/Sources/App/Frontend/WebView/WebViewController/WebViewController+URLLoading.swift b/Sources/App/Frontend/WebView/WebViewController/WebViewController+URLLoading.swift index 5c7f0ed9d6..10ad49875f 100644 --- a/Sources/App/Frontend/WebView/WebViewController/WebViewController+URLLoading.swift +++ b/Sources/App/Frontend/WebView/WebViewController/WebViewController+URLLoading.swift @@ -168,9 +168,12 @@ extension WebViewController { Current.Log.info("loading kiosk dashboard path: \(kioskURL.path)") return kioskURL } - if Current.settingsStore.restoreLastURL, let initialURL, initialURL.baseIsEqual(to: webviewURL) { - Current.Log.info("restoring initial url path: \(initialURL.path)") - return initialURL + if Current.settingsStore.restoreLastURL, webView.url == nil, let initialURLPath, + let restored = Self.restoredURL(base: webviewURL, relativePath: initialURLPath) { + // Keep the resolved full URL so the WebKit delegates can detect it finishing (or 404ing). + initialURL = restored + Current.Log.info("restoring last path: \(restored.path)") + return restored } if let currentURL = webView.url, currentURL.path.count > 1 { // Preserve the current path when the base URL changes (e.g., switching between internal/external) @@ -195,6 +198,19 @@ extension WebViewController { return webviewURL } + /// Rebuilds a stored relative reference (`path?query#fragment`) onto the currently active base URL, so + /// a page saved on one network/location reopens correctly on another. Non-private for tests. + static func restoredURL(base: URL, relativePath: String) -> URL? { + guard var components = URLComponents(url: base, resolvingAgainstBaseURL: true), + let relative = URLComponents(string: relativePath) else { + return nil + } + components.path = relative.path + components.query = relative.query + components.fragment = relative.fragment + return components.url + } + /// The URL of the kiosk-configured dashboard for this server, or `nil` when kiosk mode is off, this /// isn't the kiosk server, or no specific dashboard was chosen (in which case the server default loads). private func kioskDashboardURL(for webviewURL: URL) async -> URL? { diff --git a/Sources/App/Frontend/WebView/WebViewController/WebViewController+WebViewSetup.swift b/Sources/App/Frontend/WebView/WebViewController/WebViewController+WebViewSetup.swift index 981570b47f..cb5504ba58 100644 --- a/Sources/App/Frontend/WebView/WebViewController/WebViewController+WebViewSetup.swift +++ b/Sources/App/Frontend/WebView/WebViewController/WebViewController+WebViewSetup.swift @@ -55,6 +55,20 @@ extension WebViewController { RestorableStateKey.server.rawValue: server.identifier.rawValue, ] userActivity?.becomeCurrent() + + // Persist the server and a host-agnostic path so cold launch reopens here; the base URL is + // re-resolved from current connectivity at load time (see `resolvedLoadURL`). + Current.settingsStore.lastActiveServerIdentifier = server.identifier.rawValue + if let components = URLComponents(url: cleanURL, resolvingAgainstBaseURL: false) { + var relative = components.path.isEmpty ? "/" : components.path + if let query = components.query { + relative += "?\(query)" + } + if let fragment = components.fragment { + relative += "#\(fragment)" + } + Current.settingsStore.lastActiveURLPath = relative + } } } } diff --git a/Sources/App/Frontend/WebView/WebViewController/WebViewController.swift b/Sources/App/Frontend/WebView/WebViewController/WebViewController.swift index 55666d580a..e04679cb65 100644 --- a/Sources/App/Frontend/WebView/WebViewController/WebViewController.swift +++ b/Sources/App/Frontend/WebView/WebViewController/WebViewController.swift @@ -27,6 +27,7 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg var latestLoadError: Error? var initialURL: URL? + var initialURLPath: String? var statusBarButtonsStack: UIStackView? var lastNavigationWasServerError = false var didHandleServerErrorResponse = false @@ -95,7 +96,9 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg } #if targetEnvironment(macCatalyst) - override var canBecomeFirstResponder: Bool { true } + override var canBecomeFirstResponder: Bool { + true + } override var keyCommands: [UIKeyCommand]? { func prioritised(_ input: String, _ action: Selector) -> UIKeyCommand { @@ -285,7 +288,7 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg onWebViewLoaded?(self) } - // Workaround for webview rotation issues: https://github.com/Telerik-Verified-Plugins/WKWebView/pull/263 + /// Workaround for webview rotation issues: https://github.com/Telerik-Verified-Plugins/WKWebView/pull/263 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in diff --git a/Sources/Shared/Settings/SettingsStore.swift b/Sources/Shared/Settings/SettingsStore.swift index 1577d7a84c..068c7a7d10 100644 --- a/Sources/Shared/Settings/SettingsStore.swift +++ b/Sources/Shared/Settings/SettingsStore.swift @@ -208,6 +208,24 @@ public class SettingsStore { } } + public var lastActiveServerIdentifier: String? { + get { + prefs.string(forKey: "lastActiveServerIdentifier") + } + set { + prefs.set(newValue, forKey: "lastActiveServerIdentifier") + } + } + + public var lastActiveURLPath: String? { + get { + prefs.string(forKey: "lastActiveURLPath") + } + set { + prefs.set(newValue, forKey: "lastActiveURLPath") + } + } + public func hasSeenWhatsNew(releaseID: String) -> Bool { seenWhatsNewReleaseIDs.contains(releaseID) } diff --git a/Tests/App/Container/OnboardingStateObservableTests.swift b/Tests/App/Container/OnboardingStateObservableTests.swift new file mode 100644 index 0000000000..6fe95d3054 --- /dev/null +++ b/Tests/App/Container/OnboardingStateObservableTests.swift @@ -0,0 +1,77 @@ +@testable import HomeAssistant +@testable import Shared +import XCTest + +@MainActor +final class OnboardingStateObservableTests: XCTestCase { + private var previousServers: ServerManager! + private var firstServer: Server! + private var secondServer: Server! + + override func setUp() { + super.setUp() + previousServers = Current.servers + let servers = FakeServerManager(initial: 0) + firstServer = servers.add(identifier: .init(rawValue: "server-1"), serverInfo: .fake()) + secondServer = servers.add(identifier: .init(rawValue: "server-2"), serverInfo: .fake()) + Current.servers = servers + + resetPersistedState() + } + + override func tearDown() { + Current.servers = previousServers + resetPersistedState() + super.tearDown() + } + + private func resetPersistedState() { + Current.settingsStore.restoreLastURL = true + Current.settingsStore.lastActiveServerIdentifier = nil + Current.settingsStore.lastActiveURLPath = nil + } + + func testPreferredInitialServerReturnsPersistedServerWhenPresent() { + Current.settingsStore.lastActiveServerIdentifier = "server-2" + + XCTAssertEqual(OnboardingStateObservable.preferredInitialServer()?.identifier, secondServer.identifier) + } + + func testPreferredInitialServerFallsBackToFirstWhenNoneStored() { + Current.settingsStore.lastActiveServerIdentifier = nil + + XCTAssertEqual(OnboardingStateObservable.preferredInitialServer()?.identifier, firstServer.identifier) + } + + func testPreferredInitialServerFallsBackToFirstWhenStoredServerMissing() { + Current.settingsStore.lastActiveServerIdentifier = "server-removed" + + XCTAssertEqual(OnboardingStateObservable.preferredInitialServer()?.identifier, firstServer.identifier) + } + + func testRestoredInitialPathReturnsStoredPathForMatchingServerWhenEnabled() { + Current.settingsStore.restoreLastURL = true + Current.settingsStore.lastActiveServerIdentifier = "server-2" + Current.settingsStore.lastActiveURLPath = "/lovelace/kitchen" + + XCTAssertEqual(OnboardingStateObservable.restoredInitialPath(for: secondServer), "/lovelace/kitchen") + } + + func testRestoredInitialPathIsNilWhenRememberLastPageOff() { + Current.settingsStore.restoreLastURL = false + Current.settingsStore.lastActiveServerIdentifier = "server-2" + Current.settingsStore.lastActiveURLPath = "/lovelace/kitchen" + + XCTAssertNil(OnboardingStateObservable.restoredInitialPath(for: secondServer)) + } + + func testRestoredInitialPathIsNilWhenServerDoesNotMatchStored() { + // Launch fell back to the first server after the saved one was removed: it must not inherit the + // path that belonged to the removed server. + Current.settingsStore.restoreLastURL = true + Current.settingsStore.lastActiveServerIdentifier = "server-2" + Current.settingsStore.lastActiveURLPath = "/lovelace/kitchen" + + XCTAssertNil(OnboardingStateObservable.restoredInitialPath(for: firstServer)) + } +} diff --git a/Tests/App/WebView/WebViewControllerTests.swift b/Tests/App/WebView/WebViewControllerTests.swift index e96dd4d32d..a70e0b11f9 100644 --- a/Tests/App/WebView/WebViewControllerTests.swift +++ b/Tests/App/WebView/WebViewControllerTests.swift @@ -228,8 +228,8 @@ final class WebViewControllerTests: XCTestCase { XCTAssertEqual(decision, .allow) } - func testServerErrorResponseDecisionReloadsDefaultURLForRestoredPage() { - let restoredURL = URL(string: "https://example.com/history")! + func testServerErrorResponseDecisionReloadsDefaultURLForRestoredPage() throws { + let restoredURL = try XCTUnwrap(URL(string: "https://example.com/history")) for statusCode in [404, 500] { let decision = WebViewController.decisionForMainFrameErrorResponse( @@ -243,8 +243,8 @@ final class WebViewControllerTests: XCTestCase { } } - func testServerErrorLoadErrorCarriesFailingURL() { - let url = URL(string: "https://example.com/lovelace")! + func testServerErrorLoadErrorCarriesFailingURL() throws { + let url = try XCTUnwrap(URL(string: "https://example.com/lovelace")) let error = WebViewController.serverErrorLoadError(for: url) @@ -279,6 +279,35 @@ final class WebViewControllerTests: XCTestCase { XCTAssertEqual((sut.latestLoadError as? URLError)?.code, .badServerResponse) } + func testRestoredURLRebuildsSavedPathOntoLiveBaseIgnoringSavedHost() throws { + // A path saved on the internal base is restored against whatever base is active now (e.g. remote + // UI), so only path/query/fragment carry over -- never the host. + let restored = try WebViewController.restoredURL( + base: XCTUnwrap(URL(string: "https://remote.example.com:8123")), + relativePath: "/lovelace/kitchen" + ) + + XCTAssertEqual(restored, URL(string: "https://remote.example.com:8123/lovelace/kitchen")) + } + + func testRestoredURLPreservesQueryAndFragment() throws { + let restored = try WebViewController.restoredURL( + base: XCTUnwrap(URL(string: "http://homeassistant.local:8123")), + relativePath: "/history?back=1#anchor" + ) + + XCTAssertEqual(restored, URL(string: "http://homeassistant.local:8123/history?back=1#anchor")) + } + + func testRestoredURLHandlesRootPath() throws { + let restored = try WebViewController.restoredURL( + base: XCTUnwrap(URL(string: "http://homeassistant.local:8123")), + relativePath: "/" + ) + + XCTAssertEqual(restored, URL(string: "http://homeassistant.local:8123/")) + } + private func makeSUT(server: Server = .fake()) -> WebViewController { let sut = WebViewController(server: server) let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 640)) From b3fca210fbcbf0ae8620224053991397ed3db743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:46:41 +0200 Subject: [PATCH 2/2] Keep HomeAssistantView conforming to WebFrontendView The added initialPath initializer used a default argument, which does not satisfy the protocol's init(server:onWebViewController:) requirement. Provide the exact required initializer delegating to the initialPath variant. --- .../WebView/HomeAssistantView/HomeAssistantView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift index d3cc698ab0..0baee8d0ca 100644 --- a/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift +++ b/Sources/App/Frontend/WebView/HomeAssistantView/HomeAssistantView.swift @@ -7,7 +7,11 @@ struct HomeAssistantView: View, WebFrontendView { @Environment(\.accessibilityReduceMotion) private var reduceMotion - init(server: Server, initialPath: String? = nil, onWebViewController: @escaping (WebViewController) -> Void) { + init(server: Server, onWebViewController: @escaping (WebViewController) -> Void) { + self.init(server: server, initialPath: nil, onWebViewController: onWebViewController) + } + + init(server: Server, initialPath: String?, onWebViewController: @escaping (WebViewController) -> Void) { _viewModel = StateObject( wrappedValue: HomeAssistantViewModel( server: server,