Skip to content

Fix(macos/ios): Add handler for web content process termination (fix #14371) - #14523

Merged
FabianLars merged 31 commits into
tauri-apps:devfrom
JeffTsang:fix/web-content-process-termination
Apr 14, 2026
Merged

Fix(macos/ios): Add handler for web content process termination (fix #14371)#14523
FabianLars merged 31 commits into
tauri-apps:devfrom
JeffTsang:fix/web-content-process-termination

Conversation

@JeffTsang

Copy link
Copy Markdown
Contributor

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.

@JeffTsang
JeffTsang requested a review from a team as a code owner November 23, 2025 09:11
@github-actions

github-actions Bot commented Nov 23, 2025

Copy link
Copy Markdown
Contributor

Package Changes Through e8991c2

There 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 Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
tauri-utils 2.8.3 2.9.0
tauri-macos-sign 2.3.3 2.3.4
tauri-bundler 2.8.1 2.9.0
tauri-runtime 2.10.1 2.11.0
tauri-runtime-wry 2.10.1 2.11.0
tauri-codegen 2.5.5 2.5.6
tauri-macros 2.5.5 2.5.6
tauri-plugin 2.5.4 2.5.5
tauri-build 2.5.6 2.5.7
tauri 2.10.3 2.11.0
@tauri-apps/cli 2.10.1 2.11.0
tauri-cli 2.10.1 2.11.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

@JeffTsang

Copy link
Copy Markdown
Contributor Author

@FabianLars Now that wry has been updated, would it be possible to take a look at this PR?

@zakxxi

zakxxi commented Feb 20, 2026

Copy link
Copy Markdown

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!

@JeffTsang

Copy link
Copy Markdown
Contributor Author

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:

document.body.style.display='none';
document.body.offsetHeight;
document.body.style.display='';

///
/// - **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>(

@velocitysystems velocitysystems Mar 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: webViewWebContentProcessDidTerminate is the name of the underlying WebKit API. I see that on_web_content_process_terminate is the naming used in WRY, but ideally this should follow the same naming as WebKit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@JeffTsang

Copy link
Copy Markdown
Contributor Author

@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.

@JeffTsang

Copy link
Copy Markdown
Contributor Author

@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.

@velocitysystems velocitysystems left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@FabianLars

Copy link
Copy Markdown
Member

Can all the commits be collapsed into a single fix: ... commit? Rather than merging the dev branch multiple times, it may be better to rebase in the future which avoids merge commits.

Please don't unless required for something. I hate force pushing since it makes it really hard to follow the thought process etc.

This is likely a a minor:feat not a patch:bug since this is an additive change

with the newly exposed api you're right

I'm still concerned about the reload() vs navigate() with URL reconstruction.

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?

@JeffTsang

Copy link
Copy Markdown
Contributor Author

I've reverted to reloading in the default handler.

@velocitysystems

velocitysystems commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

I'm going to defer to @FabianLars at this point. I just have two remaining concerns:

  1. .lock().unwrap() in the recovery callback. Can this be improved and/or made more resilient?
  2. Silent failures with no logging makes debugging difficult

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.

@JeffTsang

Copy link
Copy Markdown
Contributor Author
  1. Unwrapping the result of a mutex lock is standard and intentional, so I wouldn't be concerned with that.
  2. I've added logging to the default handler.

Adding tests would be good, but it would first require a significant refactor of prepare_pending_webview in tauri and create_webview in tauri-runtime-wry. This would involve extracting functions, deciding where to place those functions, and deciding what to name those functions. These decisions should be made by the Tauri team.

@JeffTsang
JeffTsang requested a review from FabianLars April 10, 2026 06:03
@FabianLars

Copy link
Copy Markdown
Member

I don't know why it took so long for such a serious issue to be picked up, but I was ready to patch wry for as long as I used Tauri.

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).
This pr is imo now good enough and we can iterate over it later if needed (since it's a crash handler we'll need some real world results anyway).

Apologies for the long delays, bad feedback loop, and weird moods again and thanks for working through it.

@FabianLars
FabianLars merged commit 1063c48 into tauri-apps:dev Apr 14, 2026
19 checks passed
@FabianLars FabianLars added this to the 2.11 milestone Apr 14, 2026
@wangran-creator

Copy link
Copy Markdown

Hello, Mr FabianLars . Is this merge will be build into new tauri version ? when us developers can use it.
Thank you for your contribution!

@FabianLars

Copy link
Copy Markdown
Member

Will be part of 2.11 which is scheduled for end of this week or early next week.

@wangran-creator

Copy link
Copy Markdown

Hi Mr. FabianLars
Our Team is waiting for 2.11 due to web content process termination. we want to know when we can use 2.11 >.<
Waiting for your reply...
Thank you very much!

@FabianLars

Copy link
Copy Markdown
Member

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

@wangran-creator

Copy link
Copy Markdown

Thank you very much. Which version can I use to avoid web content process termination?

@petersamokhin

Copy link
Copy Markdown
Contributor

I confirm this fixes a very critical issue that I thought was too magical to fix in a stable way.
You can reproduce it with pkill -9 -f 'com.apple.WebKit.WebContent': before this fix, the window just stays empty, but with this fix it reloads properly. Great job and thanks a lot @JeffTsang @FabianLars!

tlongwell-block added a commit to block/buzz that referenced this pull request May 15, 2026
…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>
tlongwell-block added a commit to block/buzz that referenced this pull request May 15, 2026
…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>
tlongwell-block added a commit to block/buzz that referenced this pull request May 15, 2026
…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>
@gzlboy

gzlboy commented May 17, 2026

Copy link
Copy Markdown

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.

@velocitysystems

Copy link
Copy Markdown
Contributor

@gzlboy This is already merged and available in 2.11.0.

@gzlboy

gzlboy commented May 18, 2026

Copy link
Copy Markdown

Thank you so much for your reply. I have tested it on the tauri(2.11.2) and it is fixed!

@gzlboy This is already merged and available in 2.11.0.

@MCPlummet MCPlummet mentioned this pull request Aug 5, 2026
8 tasks
MCPlummet added a commit to MCPlummet/quark that referenced this pull request Aug 12, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] iOS webview becomes blank and unresponsive when resuming from the background [bug] TAURI UI NOT WORKING PROPERLY AFTER SYSTEM AWAKE FROM SLEEP.

7 participants