Fix(macos/ios): Add handler for web content process termination (fix #14371) - #14523
Conversation
Package Changes Through e8991c2There are 9 changes which include tauri-macos-sign with patch, tauri-build with patch, tauri with minor, tauri-runtime with minor, tauri-runtime-wry with minor, tauri-bundler with minor, tauri-cli with minor, @tauri-apps/cli with minor, tauri-utils with minor Planned Package VersionsThe following package releases are the planned based on the context of changes in this pull request.
Add another change file through the GitHub UI by following this link. Read about change files or the docs at github.com/jbolda/covector |
|
@FabianLars Now that wry has been updated, would it be possible to take a look at this PR? |
|
Hey 👋 Just wanted to check in on the status of this PR. I'm building a mobile app with SvelteKit + Tauri and experiencing this exact issue on iOS › the webview goes sometime blank after resuming from background. This fix would be really helpful. Is there anything blocking the review? Thanks for the work on this! |
|
Unfortunately, it looks like this PR won't be reviewed any time soon unless someone can bring this to the Tauri team's attention. In the meantime, you can patch wry and add your own code to the web_content_process_did_terminate function in src/wkwebview/navigation.rs You may also need to run the following code on your webview when your app enters the foreground: |
| /// | ||
| /// - **Linux / Windows / Android:** Unsupported. | ||
| #[cfg(any(target_os = "macos", target_os = "ios"))] | ||
| pub fn on_web_content_process_terminate<F: Fn(Webview<R>) + Send + 'static>( |
There was a problem hiding this comment.
When I was naming the function for wry, I opted for the shorter name because it meant the same thing. I would have kept the original name if there was a webViewWebContentProcessWillTerminate event.
|
@velocitysystems I've added a default handler for iOS. It reloads the webview since tauri-runtime-wry doesn't have access to get_app_url. |
…ss termination on iOS
…com/JeffTsang/tauri into fix/web-content-process-termination
|
@velocitysystems I've moved the default handler so that I can use navigate with a verified url. This should be a more reliable fix than reloading. |
There was a problem hiding this comment.
Thanks @JeffTsang. I've left further comments and feedback on your PR.
- There are no unit tests for the handler wiring logic.
- There is no logging during the lifecycle
Also - I'm still concerned about the reload() vs navigate() with URL reconstruction.
The default handler reconstructs the current URL, validates it against the app's base URL, and navigates to it (falling back to /index.html). This feels unnecessary — when webViewWebContentProcessDidTerminate
fires, WKWebView is in a well-defined state: the content process is gone, but the navigation state (URL history, current URL) is preserved in the UI process. reload() handles this correctly in one call.
The navigate() approach also has downsides:
- If the user was on /settings/profile, the /index.html fallback loses their place
- The string prefix check (starts_with(app_url.as_str())) is a fragile way to validate URL origin
- Two
unwrap()calls in a crash-recovery callback risk panicking the app — worse than the blank screen
Thoughts @FabianLars?
Please don't unless required for something. I hate force pushing since it makes it really hard to follow the thought process etc.
with the newly exposed api you're right
How about a very conservative approach? For the initial PR only reload the page. If the app dev wants to they can now add a handler that works for their app as they know best where to navigate to. Then we can investigate some more whether we can do something with navigation or even app restarts. This way we would at least unblock this PR. Reading through the capacitor discussion, I do not reallyyy share your concerns about losing app state etc on navigation as it seems like reloading/navigation may not even be enough to restore functionality? |
|
I've reverted to reloading in the default handler. |
|
I'm going to defer to @FabianLars at this point. I just have two remaining concerns:
The lack of test coverage makes me a little uneasy but I know that will be addressed in the future. However I would love to see this PR move forward like @JeffTsang as this is a high-priority fix for us. |
Adding tests would be good, but it would first require a significant refactor of |
Simple, the last few months we were very short on maintainers and/or time, it's not like we prioritized many other issues/PRs, we didn't have time for anything really. In that spirit i don't want to drag this out any further as well. I'm not 100% sold on having the handler in the runtime crate at this point but we'd probablyyy move it there at some point when we inevitable rework how tauri's runtime crates work (for better cef, servo, etc support). Apologies for the long delays, bad feedback loop, and weird moods again and thanks for working through it. |
|
Hello, Mr FabianLars . Is this merge will be build into new tauri version ? when us developers can use it. |
|
Will be part of 2.11 which is scheduled for end of this week or early next week. |
|
Hi Mr. FabianLars |
|
it will be delayed a bit due to unforeseen absences, sorry. btw, in case you didn't know - you can use unpublished tauri versions: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories |
|
Thank you very much. Which version can I use to avoid web content process termination? |
|
I confirm this fixes a very critical issue that I thought was too magical to fix in a stable way. |
…ep on macOS macOS aggressively suspends/terminates WKWebView's WebContent process during long screen-lock/sleep. Until tauri-runtime-wry 2.11.0, there was no default handler for this, so on resume the Tauri window remained alive but the webview rendered blank with no JS context — only an app restart recovered it. tauri-runtime-wry 2.11.x ships a default macOS/iOS handler that calls webview.reload() on web content process termination (via the new wry webViewWebContentProcessDidTerminate hook). We inherit this automatically by bumping the runtime. Bumps (via cargo update -p tauri --precise 2.11.1 in desktop/src-tauri): - tauri 2.10.3 -> 2.11.1 - tauri-runtime 2.10.1 -> 2.11.1 - tauri-runtime-wry 2.10.1 -> 2.11.1 - wry 0.54.4 -> 0.55.1 - tao 0.34.8 -> 0.35.2 - tauri-build 2.5.6 -> 2.6.1 - tauri-utils 2.8.3 -> 2.9.1 - @tauri-apps/api ~2.10 -> ~2.11 - @tauri-apps/cli ~2.10 -> ~2.11 Verified: - cargo check / clippy --no-deps / fmt --check on desktop/src-tauri: clean - pnpm typecheck / lint / build on desktop/: clean Refs: tauri-apps/tauri#10662, tauri-apps/tauri#14523, tauri-apps/wry#1619 Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
…ep on macOS macOS aggressively suspends/terminates WKWebView's WebContent process during long screen-lock/sleep. Until tauri-runtime-wry 2.11.0, there was no default handler for this, so on resume the Tauri window remained alive but the webview rendered blank with no JS context — only an app restart recovered it. tauri-runtime-wry 2.11.x ships a default macOS/iOS handler that calls webview.reload() on web content process termination (via the new wry webViewWebContentProcessDidTerminate hook). We inherit this automatically by bumping the runtime. Bumps (via cargo update -p tauri --precise 2.11.1 in desktop/src-tauri): - tauri 2.10.3 -> 2.11.1 - tauri-runtime 2.10.1 -> 2.11.1 - tauri-runtime-wry 2.10.1 -> 2.11.1 - wry 0.54.4 -> 0.55.1 - tao 0.34.8 -> 0.35.2 - tauri-build 2.5.6 -> 2.6.1 - tauri-utils 2.8.3 -> 2.9.1 - @tauri-apps/api ~2.10 -> ~2.11 - @tauri-apps/cli ~2.10 -> ~2.11 CI install stability: - add pnpm fetch retry/timeout settings for transient Artifactory socket timeouts Verified: - cargo check / clippy --no-deps / fmt --check on desktop/src-tauri: clean - pnpm typecheck / lint / build on desktop/: clean Refs: tauri-apps/tauri#10662, tauri-apps/tauri#14523, tauri-apps/wry#1619 Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
…ep on macOS macOS aggressively suspends/terminates WKWebView's WebContent process during long screen-lock/sleep. Until tauri-runtime-wry 2.11.0, there was no default handler for this, so on resume the Tauri window remained alive but the webview rendered blank with no JS context — only an app restart recovered it. tauri-runtime-wry 2.11.x ships a default macOS/iOS handler that calls webview.reload() on web content process termination (via the new wry webViewWebContentProcessDidTerminate hook). We inherit this automatically by bumping the runtime. Bumps (via cargo update -p tauri --precise 2.11.1 in desktop/src-tauri): - tauri 2.10.3 -> 2.11.1 - tauri-runtime 2.10.1 -> 2.11.1 - tauri-runtime-wry 2.10.1 -> 2.11.1 - wry 0.54.4 -> 0.55.1 - tao 0.34.8 -> 0.35.2 - tauri-build 2.5.6 -> 2.6.1 - tauri-utils 2.8.3 -> 2.9.1 - @tauri-apps/api ~2.10 -> ~2.11 - @tauri-apps/cli ~2.10 -> ~2.11 Verified: - cargo check / clippy --no-deps / fmt --check on desktop/src-tauri: clean - pnpm typecheck / lint / build on desktop/: clean Refs: tauri-apps/tauri#10662, tauri-apps/tauri#14523, tauri-apps/wry#1619 Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
|
I'm encountering the same issue on iOS. This is a very serious bug, and I hope this PR can be implemented as soon as possible. |
|
@gzlboy This is already merged and available in 2.11.0. |
|
Thank you so much for your reply. I have tested it on the tauri(2.11.2) and it is fixed!
|
* fix(timeline): show image caption immediately on send (#41) Sending an image has no local echo, so the sent event paints when its own sync echo arrives (image sends are never registered in _ownSentEventIds). That live-tail path went through sync.ts's private copy of timelineEventToMessage, which never mapped `caption` — unlike the shared mapper in actions/context.ts used by the room-load path. The image therefore rendered bare until the room was reloaded, which is when the caption finally appeared. Incoming captioned images from other users hit the same gap. Map `caption` in the sync mapper, and cover the live-tail path with a regression test driving the real quark://sync/message listener. Claude-Session: https://claude.ai/code/session_018rV5Li91o4mJframDxbLBc Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(timeline): keep Today/Yesterday separator labels correct past midnight (#40) Separator labels ("Today at 5:03 PM") are relative to the current date but were baked into the DOM node when it was built, so a session left open past midnight kept rendering yesterday's separators as "Today" while today's new messages got a second "Today" label. Stash the separator's timestamp on the node and re-label every rendered separator on a one-shot timer armed for the next local midnight (re-armed after each fire, so a machine suspended over midnight relabels on wake). Also fixes the day comparison itself: "yesterday" was midnight minus 86400000ms, which misses the previous local midnight on a DST shift day (23/25 hours long) and fell through to the weekday label. Now derived with getDate() - 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): pin composer during keyboard scroll on iOS, center compose-row buttons (#33) Two remaining items from #33 (the Android keyboard-offset ones landed in 0.17.1). Compose area misbehaving when scrolled on with the keyboard open (iOS): iOS keeps the layout viewport at full height while the keyboard is up, so the visual viewport is a smaller window that can be panned within it — and any touch drag the page doesn't consume does exactly that. The compose row consumes nothing (the field only overflows past six lines; the mode indicator and button cluster never scroll), so a drag there chained straight out to a pan. trackVisualViewport folded vv.offsetTop into --keyboard-offset, which drives .content-area's padding-bottom, so every pan frame re-laid-out the whole content column: the timeline resized (and re-fired its stick-to-bottom observer) and the compose bar chased the finger. 0.17.1 dropped the CSS transition during those updates, which addressed the lag but not the relayout or the pan itself. - Split the visual-viewport geometry in mobile.ts: --keyboard-offset is now just the inset the keyboard takes (resize-driven, safe to lay out with), and the pan ships separately as --viewport-pan, compensated by a transform on #app. Compositor-only; nothing reflows while dragging. The pan is ignored when no keyboard is up so a deliberate pinch-zoom pan isn't cancelled out, and clamped to the inset so an overscroll can't push past it. Android is unaffected either way: adjustResize shrinks the layout viewport too, leaving both values at 0. - Give the compose row nothing to hand a drag to: the field keeps its own scrolling (touch-action: pan-y) but stops chaining onward (overscroll-behavior: contain), and the chrome around it takes no pan. - Drop the now-unused .quark-kb-immediate escape hatch — the offset no longer changes on scroll, so the open/close transition can just run. Compose-row buttons not centered vertically on mobile: the compose box is deliberately lopsided — a top-only padding wrapping a field whose own padding is bottom-heavy (4/8) — so "centered in the content box" lands ~3px below the pill's optical middle, which is why 0.17.1's align-self wasn't enough. Mobile makes it obvious because the field is forced to >=16px while the box padding still derives from the smaller desktop --font-size. Make the geometry symmetric instead of compensating for it: drop the box's top padding on mobile, split the field's 12px of vertical padding evenly (same total, so Input.ts's auto-grow and max-height math is untouched), and let the box center every child. The four controls also get one uniform box so their differently-sized glyphs (16px emoji, 10px GIF badge, 16px paperclip, 15px send arrow) share that center line — and a squarer tap target with it. Desktop is untouched throughout. Tests: viewportMetrics is exported and unit-tested for the inset/pan split (including the Android and pinch-zoom cases), plus DOM tests that the two custom properties track resize and scroll independently. Input.test.ts guards that every control in the cluster carries the class the centering rule targets, and that the send button's visibility toggle still falls through to the stylesheet. Not verified on device — needs an iOS and an Android pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ios): recover from WebKit content-process termination on resume (#39) iOS reclaims memory by killing the WKWebView's content process while an app is backgrounded. The app process itself survives, so reopening Quark handed back a live window wrapping a dead page — a blank screen that nothing but a force-quit cleared. Tauri 2.11 implements WebKit's `webViewWebContentProcessDidTerminate:` and reloads the webview in response (tauri-apps/tauri#14523); the pinned 2.10.3 has no reference to it anywhere. wry has exposed the delegate since 0.54, but it only forwards to a handler that tauri-runtime-wry 2.10.1 never installs, so the callback fired into a `None` and the page stayed blank. Bump the lock to tauri 2.11.5 and raise the manifest requirement to 2.11, so this can't silently regress back below the fix. The reload lands where it should: `restore_session` reads the session back out of the keyring and the room list repaints, and `start_sync` aborts any running loop before spawning one, so recovery can never leave two sync loops polling the homeserver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: bump version to 0.17.2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018rV5Li91o4mJframDxbLBc * fix(mobile): close the compose row's unguarded left edge (#33) The pan guard from c4c40ca was correct in intent but landed on an element that mobile never lets you touch. initMobile forces vim off on mobile, so the indicator is always in its hidden state, and Input.setVimMode hid it with an inline `visibility: hidden` — which takes an element out of hit testing entirely. `body.quark-mobile .input-bar__mode { touch-action: none }` was therefore guarding a surface no touch could ever land on. Drags there fell through to .input-bar, which takes no touch-action of its own (it is the field's ancestor, and `none` there would intersect away the field's pan-y), and chained straight back out to the visual-viewport pan. That left the row's entire left edge live — 8px of .input-bar padding + 4px of margin + the 40px indicator, 52px running the full height of the row, which is exactly the strip the bug reproduces in. - Hide the indicator by paint rather than by hit test on mobile, and let its box swallow the bar's left padding and its own margins so the guarded surface reaches the row's edge. Both are scoped to the hidden state, and nothing of it is drawn there, so the geometry is invisible: the compose box's left edge does not move (16px of lead-in either way). - Move the hiding out of an inline style and into a class, so the stylesheet can pick the mechanism per platform — an inline style would beat any rule trying to put the element back into hit testing. `aria-hidden` now carries the a11y half, which `visibility` used to do implicitly. - Guard .reply-preview-bar too. It sits in the same compose area, never scrolls, and was missing from the list for the same reason — one more surface handing drags to the pan while composing a reply. Desktop is untouched: it keeps `visibility: hidden`, and all the geometry is mobile- and hidden-only. Tests: Input.test.ts guards that the indicator hides via the class and never an inline visibility (the property that makes the touch-action rule reachable at all), that aria-hidden tracks it, and that --no-vim stays in lockstep since the mobile geometry keys off it. Not verified on device — needs an iOS pass with the keyboard up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(mobile): pin the whole compose region, disable pinch-zoom (#33) Two mobile touch fixes. The compose region still panned below the box. Patching surfaces one at a time was the wrong shape of fix: after the left edge was closed, the strip under the compose box (its own 8px margin, which belongs to .input-bar) did exactly the same thing, and the box's padding ring and the wrap's safe-area strip were still waiting behind it. touch-action cannot state the rule for the region — it intersects down the tree, so `none` on .input-bar or .input-bar__compose-box takes the field's own pan-y with it, which is why the guards could only ever sit on the leaves and never on the container. So state it once where it can be stated: Input._guardAgainstViewportPan, a non-passive touchmove listener on .input-bar-wrap that swallows any drag that isn't the field scrolling itself. That covers every bare surface at once, including ones nobody has found yet, and adding chrome to the row no longer means remembering a touch-action rule. It is on the wrap rather than the document so the rest of the page keeps its passive fast path, and it only cancels the browser's default action, so the drawer swipe in touch.ts — which drives its own transform — is unaffected. The CSS geometry from the previous commit is reverted: it existed to drag one guarded element over the row's left edge, and the region guard subsumes it. The leaf touch-action rules stay as the cheap first line, stopping the gesture before it starts rather than cancelling it after. The class-based hiding of the mode indicator stays too — it beats an inline style on its own merits, and aria-hidden with it. Pinch-zoom was enabled on the timeline. The viewport meta never carried user-scalable=no, and `body { touch-action: pan-x pan-y }` cannot cover for it: WebKit does not let touch-action suppress the page-level pinch gesture on iOS. Two comments in base.css already assumed the meta was doing this. Zooming the shell only ever misaligned the UI against a fixed compose bar and drawer. Images keep pinch-to-zoom — ImageLightbox implements its own in JS, which the meta does not affect — and desktop ignores the meta entirely. Tests: Input.pan.test.ts covers each surface touch-action cannot reach (the wrap, the bar, the compose box), that the field is left alone so it can still scroll its overflow, and that the guard is inert on desktop. In its own file because mocking ../app/mobile.js is module-wide, as in Timeline.links.test.ts. DESIGN.md gains a "Mobile touch behaviour" section for both rules. Not verified on device — needs an iOS pass with the keyboard up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ios): sync project.yml's bundle version with the rest of the tree project.yml has carried CFBundleShortVersionString 0.5.0 since the initial public release (71a99dc) while every other file that names the version moved on; it now reads 0.17.2 like package.json, Cargo.toml, tauri.conf.json, Cargo.lock and the README badge. Nothing was shipping wrong: the tauri iOS build writes the version into quark_iOS/Info.plist from tauri.conf.json, so the stale literal was overridden every time and the installed app carried the right version. It is a trap rather than a live bug — a clean xcodegen regeneration would stamp 0.5.0 into the Info.plist and the version-bump skill does not know about this file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: add a launch config for the mock frontend Runs `pnpm dev` (port 1450) against the mock IPC layer, so the UI can be exercised in a browser without a Rust build or a logged-in session — which is how the compose-region pan guard in #33 was verified outside the unit tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): raise the JS toolchain floors @tauri-apps/api ^2.0.0 → ^2.11.1, plugin-shell ^2.0.0 → ^2.3.5, @tauri-apps/cli ^2.0.0 → ^2.11.4, plus typescript ^5.9.3, vite ^6.4.3, vitest ^3.2.7 and jsdom ^25.0.1. The api/cli floors now match what src-tauri/Cargo.toml already requires: it floors the tauri crate at 2.11 for WebKit's webViewWebContentProcessDidTerminate callback, without which a backgrounded iOS app resumes to a dead content process and a blank screen (#39, DESIGN.md "Mobile lifecycle"). The JS side was still free to resolve to 2.0.x. The iOS/macOS/mobile schema churn is the CLI bump regenerating src-tauri/gen/schemas — output, not a change of intent. Tests: 674 pass across 54 files on the new versions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: stop tracking src-tauri/gen/schemas These are build output. tauri-build regenerates them from the capability and permission definitions, nothing in the repo reads them (no $schema references in src-tauri/capabilities/, no mention in CI), and they were never excluded in the first place — upstream create-tauri-app ships a src-tauri/.gitignore carrying /gen/schemas, which this repo does not have. The cost of tracking them was noise: in three years they have been touched by three commits, each time riding along with an unrelated change, and a toolchain move dirties them for reasons no reviewer can act on. This branch alone saw acl-manifests.json go dirty, then silently revert on a later rebuild. Scoped to gen/schemas deliberately. gen/apple and gen/android are hand- maintained — project.yml, Info.plist, entitlements, and the Android launcher icons that `tauri android init` does not copy for you — and must stay tracked. Verified they come back: deleting all eight and running `cargo check` regenerates the host's set (acl-manifests, capabilities, desktop, macOS). The iOS/Android/Linux ones are written when building for those targets, so a given machine only materialises what it builds — fine for files that exist for editor autocompletion. The rule follows the root .gitignore's existing convention of src-tauri/- prefixed paths rather than adding a second ignore file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(version-bump): track project.yml as a seventh version carrier The xcodegen manifest's CFBundleShortVersionString/CFBundleVersion sat at 0.5.0 until this branch synced them; add them to bump.sh's lockstep update and preflight check (each key checked independently) so the drift cannot silently recur, and list the file in SKILL.md and CLAUDE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sync): route the live tail and threads through shared mappers Delete sync.ts's drifted private copy of timelineEventToMessage and use the shared mapper from actions/context.ts, so live-tail messages gain isOwn styling, image dimensions (no decode-time layout jump), video posters, reactions, and edit state — closing the #41 bug class instead of one field of it. Add timelineEventToThreadMessage and use it in both the sync thread branch (which forwarded body-only thread replies) and openThread(), removing a third duplicate; live thread images now also download via downloadSyncMessageImage. Rename sync.caption.test.ts to sync.mapper.test.ts and grow it to 11 tests on the real mapper via an importActual partial mock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(readme): update contributing note to the seven version carriers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): close the #33 viewport-pan gaps, restore pinch-zoom The #33 defense left holes an adversarial review confirmed; close them: - Body-mounted overlays (pickers, dialogs, toasts, menus, tooltips) now mount via a shared overlay.ts helper tagging .quark-overlay, and the pan rule translates #app and the overlay layer together, so overlays no longer shear away from the panned shell. Positioners that read getBoundingClientRect() subtract the new viewportPan() to avoid double-counting. - viewportMetrics() is scale-aware: a pinch-zoomed viewport (reachable via Android force-zoom or a desktop trackpad) no longer reads as a phantom keyboard inset. - The pan guard is extracted to guardViewportPan() in mobile.ts, covers all three compose surfaces (main composer, mention/shortcode popover, thread overlay compose row), and attaches only in mobile mode so desktop keeps the passive touch fast path. - The mode indicator hides via opacity on mobile, keeping it hit-testable so its touch-action:none first defense line is live again; comments and tests now describe the real mechanism. - Pinch-zoom is restored (WCAG 1.4.4): the meta no longer sets user-scalable=no, the body-level touch-action pinch kill is gone, and ImageLightbox cancels iOS gesture events so its own pinch handling is not doubled by page zoom. - DESIGN.md's mobile touch section rewritten to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * revert(mobile): keep pinch-zoom disabled Undo the zoom re-enablement from the review-hardening commit: restore user-scalable=no/maximum-scale=1 in the viewport meta and the body-level touch-action pinch kill, and drop the now-unneeded lightbox gesture-event cancellation. viewportMetrics stays scale-aware — Android's force-enable- zoom accessibility setting bypasses the meta, so the phantom-keyboard fix still matters. DESIGN.md updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mcplummet <13141960+MCPlummet@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes #14371 and depends on tauri-apps/wry#1624
On iOS, the webview can become blank and unresponsive when resuming from the background. This can be fixed by handling the termination of the web content process. This PR adds a handler for web content process termination.