A Tauri 2 shell with one child webview per configured tool. Nav on the left with groups and a collapsible icon rail; links between configured apps switch tabs, everything else leaves for the real browser. Design spec in docs/superpowers/specs/2026-09-01-work-app-design.md.
89 lines
3.4 KiB
Markdown
89 lines
3.4 KiB
Markdown
# Work
|
|
|
|
A desktop browser for one thing only: the web tools you work in. A left nav lists them,
|
|
clicking one shows it, and links between them navigate inside the app. Everything else
|
|
opens in your real browser.
|
|
|
|
Tauri 2 · Rust · React 19 · Tailwind CSS 4 · macOS
|
|
|
|
There is no address bar, no tab strip, and no way to reach a site that is not on the list.
|
|
That is the point.
|
|
|
|
## How it works
|
|
|
|
**Every app is its own webview.** Not an iframe — Google, Microsoft and most SaaS send
|
|
`X-Frame-Options: DENY`, so an iframe-based version of this app cannot exist. Each tool
|
|
gets a real child webview (`Window::add_child`), and they all stay loaded, so switching
|
|
keeps your scroll position, your half-typed draft, and anything counting down.
|
|
|
|
**The shell measures, Rust positions.** A child webview is a native view that takes no part
|
|
in CSS layout. The React shell leaves an empty `<div id="stage">`, measures it with a
|
|
`ResizeObserver`, and reports the rect; Rust sizes the active webview to it and hides the
|
|
rest. It also means a native view paints over anything the shell draws, which is why
|
|
opening Settings hides the stage first.
|
|
|
|
**Links are routed by intent, not by URL alone.** An injected script catches genuine user
|
|
clicks and `target=_blank`, and only those. A URL outside the app's own hosts is handed to
|
|
Rust over a made-up `workapp-route:` scheme — deliberately not Tauri IPC, which would mean
|
|
granting google.com the ability to call into this app. Rust then decides:
|
|
|
|
| Target | What happens |
|
|
|---|---|
|
|
| The current app's hosts | Nothing — ordinary navigation |
|
|
| A known identity provider | Stays inside, so SSO can complete |
|
|
| Another app on your list | Switches to that app and navigates it |
|
|
| Anything else | Opens in your default browser |
|
|
|
|
Redirects are never blocked. `on_navigation` returns `true` for everything that is not the
|
|
sentinel, because a strict navigation filter breaks every OAuth chain the moment it bounces
|
|
through `accounts.google.com`.
|
|
|
|
**Sessions persist.** Each webview keeps its cookies across restarts, so you log into a
|
|
tool once. Every app also claims a real Chrome user agent by default, because Google
|
|
refuses logins from anything it identifies as an embedded webview.
|
|
|
|
## Apps and groups
|
|
|
|
An app owns the **exact host** of its URL — `mail.google.com`, not `google.com` — or Gmail
|
|
and Drive would each swallow the other's links. Where two scopes match, the longest wins.
|
|
Extra hosts can be added per app.
|
|
|
|
Groups are for the nav only. Deleting one keeps its apps, ungrouped: deleting a folder
|
|
should never be a way to lose the things inside it.
|
|
|
|
The nav collapses to a 52px icon rail that is still clickable, so switching apps never
|
|
requires expanding it first.
|
|
|
|
## Running it
|
|
|
|
```bash
|
|
npm install && npm run tauri dev
|
|
```
|
|
|
|
## Shipping it
|
|
|
|
Build, replace the copy in `/Applications`, and relaunch — one command:
|
|
|
|
```bash
|
|
npm run ship
|
|
```
|
|
|
|
No disk image; nothing here is being distributed.
|
|
|
|
## Configuration
|
|
|
|
`apps.json`, under `~/Library/Application Support/com.vincent.workapp/`. Hand-editing it is
|
|
supported — missing fields fall back to their defaults, and an app with no `scope` gets its
|
|
URL's host.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
cd src-tauri && cargo test
|
|
```
|
|
|
|
Routing and config are pure and carry real tests: scope matching and its precedence,
|
|
identity-provider passthrough, the routing decisions, sentinel round-trips, and config
|
|
round-trips. Webview orchestration and bounds sync have no seam a unit test can reach and
|
|
are verified by running the app.
|