From ebfe55f909a21c498fe565868f55923cdf516629 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 29 Dec 2025 17:13:31 +0100 Subject: [PATCH] fix: enable canvas webview scrolling on mobile nodes --- AGENTS.md | 2 ++ CHANGELOG.md | 1 + .../com/steipete/clawdis/node/ui/RootScreen.kt | 4 ++++ apps/ios/Sources/Screen/ScreenController.swift | 14 +++++++++++--- apps/ios/Tests/ScreenControllerTests.swift | 9 +++++++++ src/canvas-host/a2ui/.bundle.hash | 2 +- 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a8d574840..0abed7948 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,8 @@ - Also read the shared guardrails at `~/Projects/oracle/AGENTS.md` and `~/Projects/agent-scripts/AGENTS.MD` before making changes; align with any cross-repo rules noted there. - SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; don’t introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code. - **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch. +- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`. +- A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) instead of manual conflict resolution. - Notary key file lives at `~/Library/CloudStorage/Dropbox/Backup/AppStore/AuthKey_NJF3NFGTS3.p8` (Sparkle keys live under `~/Library/CloudStorage/Dropbox/Backup/Sparkle`). - **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless Peter explicitly asks (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes. - **Multi-agent safety:** when Peter says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When Peter says "commit", scope to your changes only. When Peter says "commit all", commit everything in grouped chunks. diff --git a/CHANGELOG.md b/CHANGELOG.md index 27a8716b5..ab4509c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes - macOS: Voice Wake now fully tears down the Speech pipeline when disabled (cancel pending restarts, drop stale callbacks) to avoid high CPU in the background. +- iOS/Android nodes: enable scrolling for loaded web pages in the Canvas WebView (default scaffold stays touch-first). ## 2.0.0-beta4 — 2025-12-27 diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt index 4ee3afa1a..49bbee928 100644 --- a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt +++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt @@ -163,6 +163,10 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier) // Some embedded web UIs (incl. the "background website") use localStorage/sessionStorage. settings.domStorageEnabled = true settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE + isScrollContainer = true + overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS + isVerticalScrollBarEnabled = true + isHorizontalScrollBarEnabled = true webViewClient = object : WebViewClient() { override fun onReceivedError( diff --git a/apps/ios/Sources/Screen/ScreenController.swift b/apps/ios/Sources/Screen/ScreenController.swift index 6b3003360..76a17d55a 100644 --- a/apps/ios/Sources/Screen/ScreenController.swift +++ b/apps/ios/Sources/Screen/ScreenController.swift @@ -43,9 +43,7 @@ final class ScreenController { self.webView.scrollView.contentInset = .zero self.webView.scrollView.scrollIndicatorInsets = .zero self.webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - // Disable scroll to allow touch events to pass through to canvas - self.webView.scrollView.isScrollEnabled = false - self.webView.scrollView.bounces = false + self.applyScrollBehavior() self.webView.navigationDelegate = self.navigationDelegate self.navigationDelegate.controller = self a2uiActionHandler.controller = self @@ -60,6 +58,7 @@ final class ScreenController { func reload() { let trimmed = self.urlString.trimmingCharacters(in: .whitespacesAndNewlines) + self.applyScrollBehavior() if trimmed.isEmpty { guard let url = Self.canvasScaffoldURL else { return } self.errorText = nil @@ -250,6 +249,15 @@ final class ScreenController { return false } + private func applyScrollBehavior() { + let trimmed = self.urlString.trimmingCharacters(in: .whitespacesAndNewlines) + let allowScroll = !trimmed.isEmpty + let scrollView = self.webView.scrollView + // Default canvas needs raw touch events; external pages should scroll. + scrollView.isScrollEnabled = allowScroll + scrollView.bounces = allowScroll + } + private static func jsValue(_ value: String?) -> String { guard let value else { return "null" } if let data = try? JSONSerialization.data(withJSONObject: [value]), diff --git a/apps/ios/Tests/ScreenControllerTests.swift b/apps/ios/Tests/ScreenControllerTests.swift index 028a0eae6..835c0081f 100644 --- a/apps/ios/Tests/ScreenControllerTests.swift +++ b/apps/ios/Tests/ScreenControllerTests.swift @@ -16,6 +16,15 @@ import WebKit #expect(scrollView.bounces == false) } + @Test @MainActor func navigateEnablesScrollForWebPages() { + let screen = ScreenController() + screen.navigate(to: "https://example.com") + + let scrollView = screen.webView.scrollView + #expect(scrollView.isScrollEnabled == true) + #expect(scrollView.bounces == true) + } + @Test @MainActor func navigateSlashShowsDefaultCanvas() { let screen = ScreenController() screen.navigate(to: "/") diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index eca4af8df..23e15ecec 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -8c6030afb0b9f264b0bb9dcfb738b67d361fc5acac7967b4e056169a44f95184 +401ee2e7aa55e8abfd5e8ee94810a75629253b88371dc70f5b04c4830cdcce8d