# 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 `
`, 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.