Work App: a dedicated browser for work tools
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.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user