Drop cookie import; click-through notifications; per-app zoom

The cookie import is gone. It worked mechanically - 43 cookies decrypted
from Arc and verifiably visible to the page - but Google, Microsoft and
Odoo all refused the imported sessions, because each binds a session to
the browser that created it. Signing in once inside the app is simpler
and actually works, so the whole path is deleted rather than kept as a
feature that mostly fails. That takes rusqlite, aes, cbc, pbkdf2, hmac,
sha1 and sha2 out of the build with it.

Notifications are now raised through mac-notification-sys rather than
Tauri's notification plugin, because the plugin cannot report that one
was clicked. A click switches to the app that raised it and then runs
the page's own click handler - the only thing that knows which message
the notification was about.

Zoom is per app, on a fixed ladder so Cmd+0 returns to exactly 100%.
The shortcuts are menu-bar accelerators rather than a key listener,
since the keystroke has to work while a remote page has focus.

The hidden-element count is off the nav rows.
This commit is contained in:
2026-09-01 12:53:52 +02:00
parent f057268103
commit 3525d454bf
18 changed files with 416 additions and 1073 deletions
@@ -21,7 +21,7 @@ Settled during brainstorming. Not open questions.
|---|---|---|
| Shell | Tauri 2.11 · React 19 · Vite 7 · Tailwind 4 · TypeScript | Matches FlightTube, the reference app on this machine |
| App rendering | One child webview per app (`add_child`) | Iframes are impossible: Google, Microsoft and most SaaS send `X-Frame-Options: DENY` |
| Sessions | Persistent per-app cookie jar, plus opt-in import from a paired browser | The jar is mandatory either way; the import only saves first logins |
| Sessions | Persistent per-app cookie jar | You sign in once per tool and it sticks |
| Link routing | Injected JS click interceptor; permissive `on_navigation` | A strict navigation filter breaks every OAuth redirect chain |
| Tab memory | Every app is a live webview, hidden when inactive | Keeps scroll position, drafts and timers across switches |
| Collapsed nav | ~52px icon rail | Still clickable when collapsed |
@@ -32,9 +32,11 @@ Settled during brainstorming. Not open questions.
- **Multi-webview is `unstable` in Tauri.** The API can change between minor versions, so
`tauri` is pinned to `=2.11.5`.
- **Device-bound sessions defeat cookie import.** Google and Microsoft increasingly bind a
session to the browser that created it. Imported cookies will sometimes be rejected and
the tool asks for a real login once. The persistent jar keeps it from then on.
- **Browser cookie import was built, measured, and removed.** It worked mechanically —
43 cookies decrypted from Arc and verifiably visible to the page — but Google, Microsoft
and Odoo all refused the imported sessions, because each binds a session to the browser
that created it. Signing in once inside the app is both simpler and more reliable, so
the whole import path was deleted rather than kept as a feature that mostly fails.
- **Bounds sync trails layout by a frame.** A native webview is positioned from measurements
the shell reports, so during a window resize it can lag. Every app in this class does.
- **The Chrome user agent is a lie.** Sites that sniff deeply may behave oddly. It is on by
@@ -137,30 +139,6 @@ for the top bar. Redirects, meta-refreshes and OAuth bounces are never blocked.
`window.open` cannot escape into a stray window. A `_blank` link within the same app
navigates that app's webview in place.
### Browser pairing
Settings lists the browsers actually installed. Pairing with a Chromium browser:
1. Copy the profile's `Cookies` SQLite file — Chrome holds a lock on the original — and
read it with `rusqlite`.
2. Read the `Chrome Safe Storage` key from the login Keychain, derive AES-128 with
PBKDF2-HMAC-SHA1 (salt `saltysalt`, 1003 iterations), and decrypt the `v10` values.
3. Keep only cookies whose domain matches a configured app's scope or the identity-provider
list. Nothing else is read out of the browser.
4. Inject them into `WKHTTPCookieStore`.
**Verified on the machine.** Pairing against Arc imported 43 cookies across 9 domains,
and a probe from inside the page then read back `host=example.odoo.com
names=tz,cids,frontend_lang visible=3``tz` existing only in Arc's store, which is what
proves the import landed rather than merely being handed over. `setCookie` is
fire-and-forget, so the import's own count could never have shown this.
The sites still presented sign-in pages. That is the documented limitation, not a broken
import: the cookies are in the store and visible to the page, and the session is being
refused at the far end. Settings keeps the probe as **Check cookies**, so the same
question can be answered again without guessing.
Pairing is a button, not a background job. Cookies rotate; a silent task that periodically
reaches into the Keychain is worse than one the user presses when something logs them out.
## UI
@@ -201,8 +179,20 @@ Verified on the machine: `permission: Granted · direct: raised · page: api=fun
was the reading that showed the native API existed and the shim had therefore never
installed. The shim now replaces it unconditionally.
A click on the banner switches to the app that raised it and then runs the page's own
click handler, which is the only thing that knows which message the notification was
about. This is why notifications are raised through `mac-notification-sys` rather than
Tauri's notification plugin: the plugin cannot report a click.
Service-worker push is **not** covered — only notifications a page raises while it is open.
## Zoom
Per app, on a fixed ladder so ⌘0 returns to exactly 100% rather than to whatever a
repeated multiplier happened to leave behind. The shortcuts are menu-bar accelerators, not
a key listener: the keystroke has to work while an app's own webview has focus, and that
webview is a remote page this app deliberately cannot script for input.
## Hiding elements
Anything on a page can be right-clicked and hidden. The injected script owns a stylesheet