Skip to content

fix(portable): macOS webview profile cleanup & UI notice (Addresses #227) - #267

Merged
tonhowtf merged 1 commit into
tonhowtf:mainfrom
ManoloZocco:fix/macos-portable-mode
Jul 31, 2026
Merged

fix(portable): macOS webview profile cleanup & UI notice (Addresses #227)#267
tonhowtf merged 1 commit into
tonhowtf:mainfrom
ManoloZocco:fix/macos-portable-mode

Conversation

@ManoloZocco

Copy link
Copy Markdown
Contributor

Addresses #227

Summary

On macOS, WebKit/Wry creates WebView session data in ~/Library/Application Support/wtf.tonho.omniget, ~/Library/Caches/wtf.tonho.omniget, and ~/Library/WebKit/wtf.tonho.omniget even when OMNIGET_PORTABLE=1 is set, because WKWebView hardcodes these system directories.

This PR provides a hybrid mitigation on macOS:

  1. Automated Profile Cleanup (Backend): When running in portable mode on macOS (OMNIGET_PORTABLE=1), the app automatically cleans up created WebView profile/cache directories under ~/Library both at app startup (for crash recovery) and upon exit.
  2. Transparent UI Notice (Frontend): In Settings > Storage, a dedicated section displays the active portable mode status along with an inline informational notice explaining macOS WKWebView behavior and automatic session cleanup upon exit.

Key Changes

  • Rust Backend (src-tauri/src/core/portable.rs):
    • Added cleanup_macos_portable_webview_dirs() to purge ~/Library/Application Support/wtf.tonho.omniget, ~/Library/Caches/wtf.tonho.omniget, and ~/Library/WebKit/wtf.tonho.omniget.
    • Added unit test cleanup_removes_target_dirs covering path cleanup logic.
  • Lifecycle Integration (src-tauri/src/main.rs, src-tauri/src/lib.rs):
    • Invoked cleanup during startup inside check_portable_mode().
    • Invoked cleanup on exit in RunEvent::ExitRequested.
  • IPC Command (src-tauri/src/commands/app_lifecycle.rs):
    • Added get_portable_info IPC command returning is_portable, data_dir, and macos_webview_notice flag.
  • Frontend & Navigation (src/routes/settings/+page.svelte, src/components/settings/SettingsStorage.svelte):
    • Added a dedicated Storage category under the MEDIA section in the Settings sidebar.
    • Added an inline Portable Mode status card showing the active data directory and the macOS cleanup notice with multi-line text wrapping.
  • i18n (src/lib/i18n/*.json):
    • Added translation keys (portable_label, portable_active, portable_macos_notice, cat_storage) across all 10 locales using proper {{dir}} interpolation syntax.
  • CI (.github/workflows/ci.yml):
    • Added macos-latest to the portable mode smoke test matrix.

Verification

  • Executed unit tests (cargo test --manifest-path src-tauri/Cargo.toml): 344/344 passing.
  • Executed frontend checks (pnpm check && pnpm test && pnpm check:i18n): 0 errors, all 10 locales in sync.
  • Verified live app UI with OMNIGET_PORTABLE=1 OMNIGET_DATA_DIR=$(pwd)/data cargo tauri dev.

@tonhowtf tonhowtf left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for taking a swing at #227 — the write-up is thorough and I can tell you read the issue. But I have to request changes, and it's not cosmetic: the core mechanism here can destroy user data, and I don't think a cleanup-based approach can ever deliver what portable mode promises. Let me walk through why.

1. This deletes the installed app's data. On macOS, ~/Library/Application Support/wtf.tonho.omniget is not "WebView session data" — it's OmniGet's primary data directory for a normal (non-portable) install: settings.json, the SQLite history DB, cookie storage, plugins/, bin/. The actual WKWebView data lives under ~/Library/WebKit/<bundle-id> and Caches. So the concrete failure mode is: a user has OmniGet installed on their Mac, runs a portable copy from a USB stick once, and cleanup_macos_portable_webview_dirs() wipes their installed app's settings, history, cookies and plugins — at startup, before a window even opens, and again at exit. The cleanup has no way to tell "portable session residue" apart from "the installed app's data", because on macOS they're the same paths. That alone is a blocker.

2. It also breaks the legacy settings adoption. The startup call was inserted in check_portable_mode() before the block that adopts settings.json from the OS profile (main.rs, the "Older versions resolved the settings store…" block). You delete the file, then three lines later the existing code tries to copy it.

3. Write-then-delete doesn't fulfill the portable promise, even setting (1) aside. RunEvent::ExitRequested never fires on crash, SIGKILL or force-quit — and the startup "crash recovery" only helps if the portable app runs again on the same machine, which is precisely what the USB-stick user doesn't do. That's the central use case (leave no trace on a borrowed machine) failing in exactly the scenario it exists for. Even on graceful exit, WebKit's network process flushes cookies/cache during teardown and can recreate files after your remove_dir_all runs. And deletion isn't erasure — the bytes were on disk (APFS snapshots, Time Machine). The promise is "never write to the profile"; "write, then erase" is a weaker promise, and the UI copy ("automatically cleaned up when OmniGet exits") states it as stronger than it is.

4. The CI green worries me most. The smoke test asserts after exit, and the smoke exit path is graceful (OMNIGET_SMOKE_EXIT_MShandle.exit(0)ExitRequested → your cleanup runs) — so adding macos-latest passes not because the leak is fixed, but because the assertion's meaning got inverted from "never wrote to the profile" to "erased it before we looked". That test failing on macOS is the honest state until wry exposes WKWebsiteDataStore configuration. I'd rather have no macOS smoke job than a green one attesting something false. (The unexplained NODE_OPTIONS heap bump in that job should go too, or be its own PR with a reason.)

5. Scope/size. 494 of the 716 added lines are AI planning documents under docs/superpowers/ — please don't commit those. The ContextHint.svelte API change is dead code (the final SettingsStorage.svelte doesn't use the component, and the new type prop isn't used even inside it). 7 of 10 locales received raw English strings. The it/zh-TW wording fixes are fine but belong in their own PR, and the new "Storage" settings category collides with an in-progress settings-IA rework.

What I'd gladly take from this PR: the honest half. Issue #227's option 2 — get_portable_info + a notice in settings that says the webview is not portable on macOS, full stop, no cleanup, no promise of erasure — is exactly the kind of transparency I asked for, and your IPC command and UI wiring are most of the way there. If you slim this down to that (~80 lines: command, notice, en strings + scripts/sync-locales.mjs, no CI change, no docs, no ContextHint change), I'll review it quickly and happily merge. The real fix for the leak itself has to happen upstream in wry (WKWebsiteDataStore with a custom location); if you want to open that upstream issue and link it from ours, that would be genuinely useful too.

Addresses tonhowtf#227 with issue option 2: transparency, not cleanup.

Add a `get_portable_info` command and surface it in the existing Storage
settings section. When portable mode is on, the panel names the data
directory in use; on macOS it also states that the WebView keeps its
session data under ~/Library and that portable mode cannot redirect it.

The real fix belongs upstream in wry, which does not read `data_directory`
on WKWebView the way it does for WebKitGTK. Until that exists, telling the
user the truth beats letting them assume the USB stick left no trace.

Non-en locales carry the English copy for now: check:i18n --strict aborts
when a locale is missing a key en.json has, so the keys have to land in all
ten files at once.
@ManoloZocco
ManoloZocco force-pushed the fix/macos-portable-mode branch from 6c28ac6 to 2b7c4e0 Compare July 31, 2026 15:01
@ManoloZocco

Copy link
Copy Markdown
Contributor Author

Sorry — you were right on every count, and the first point is the one I feel worst about. I shipped a remove_dir_all over a directory I had assumed was WebView session data without ever checking what actually lives there. It isn't a near miss: app_data_dir() in omniget-core/src/core/paths.rs resolves to dirs::data_dir()/wtf.tonho.omniget, which on macOS is ~/Library/Application Support/wtf.tonho.omniget. So a portable copy run once from a USB stick would have deleted an installed OmniGet's settings.json, history DB, cookies and plugins/, at startup, before a window even opened. Thank you for catching that before it reached anyone.

You were also right that the green CI was the worst part, not the best. The smoke assertion ran after a graceful handle.exit(0), so it was measuring "erased before we looked", not "never wrote". I'd built the thing that made the test agree with me.

I've force-pushed a rewrite (2b7c4e00), rebased onto current main. It's issue #227 option 2 and nothing else:

  • get_portable_info reports is_portable, data_dir, and a macOS-only macos_webview_notice flag.
  • The notice renders in the existing Storage section, stating that on macOS the WebView keeps its session data under ~/Library and portable mode cannot redirect it — only OmniGet's own data is portable there, with a link back to Portable mode still writes to the user profile on macOS #227.

Everything else is gone: the cleanup helper and its test, both call sites, the tempfile dev-dependency (it existed only for that test), the macos-latest smoke matrix entry, the NODE_OPTIONS bump, the docs/superpowers/ files, the ContextHint change, and the it/zh-TW wording fixes. I also dropped the new "Storage" nav category — SettingsStorage stays where it is under Advanced, so this doesn't touch the settings IA at all.

The PR is now 14 files and 126 lines, down from 23 and 716.

Verification:

cargo check                → clean
cargo clippy --all-targets → no warnings on the touched files
pnpm check:clippy-baseline → clippy baseline OK — 92 warnings, none new
cargo fmt --check          → clean
pnpm check:i18n            → All 9 other locales in sync with en.json
pnpm check                 → 1365 FILES 0 ERRORS 107 WARNINGS

The 107 warnings are the pre-existing ones under study/*; none are new.

One deliberate deviation from your ~80-line sketch that I should flag rather than have you find: the notice strings go into all ten locale files in English, not just en.json. scripts/generate-i18n-keys.js in --strict mode (lines 98-112) aborts when any locale is missing a key that en.json has, so an en-only change would fail the i18n gate. I couldn't find scripts/sync-locales.mjs anywhere in the repo — if that's something you have in flight, I'm glad to redo this the way it expects once it lands. That accounts for most of the gap between 80 lines and 126.

On the upstream fix: agreed that this belongs in wry via WKWebsiteDataStore with a custom location. I'd like to open that issue and link it here, but I want to write it up properly rather than fire off a one-liner, so give me a little time on it.

Thanks for the detailed review — the failure mode in point 1 is one I'd rather learn from a review than from a bug report.

@tonhowtf
tonhowtf merged commit 056e5f9 into tonhowtf:main Jul 31, 2026
8 checks passed
@tonhowtf

Copy link
Copy Markdown
Owner

Merged. This is exactly the honest version the issue needed — the notice says what's true, nothing more, and 126 lines instead of 716. Owning the remove_dir_all analysis the way you did in your comment is appreciated; that kind of review response makes maintaining this easier, not harder. Keeping #227 open for the real fix upstream in wry. Ships in the next release.

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.

2 participants