Making a web app feel native on macOS
Bombay's dashboard is a Nuxt 3 SPA served by a Python backend at localhost:8765. It works fine in a browser, but opening it always felt like visiting a website rather than using an app. Two things bothered me:
- The icon was wrong. In the Dock and Finder, it showed a generic Safari compass instead of Bombay's green inbox icon.
- It felt like it needed a terminal. Even though the backend starts at login via a LaunchAgent, the mental model was "start the service, then open Safari."
Both problems had the same fix: treat the dashboard as a Progressive Web App and let Safari's Add to Dock feature do the rest.
What was missing
Safari's "Add to Dock" (File → Add to Dock) creates a standalone .app in ~/Applications that opens your site in its own window — no address bar, no tabs, no Safari chrome. But it needs two things to look right:
- A web app manifest (
manifest.json) telling the browser the app's name, theme colour, and icons - An apple-touch-icon link tag pointing to a properly sized PNG
Without these, Safari falls back to a screenshot of the page as the icon and uses a generic name. That's what Bombay had been doing.
The fix
Three files added to dashboard/public/:
| File | Purpose |
|---|---|
manifest.json |
App name, display mode, icon references |
apple-touch-icon.png |
180×180 icon for Safari and iOS |
icon-128.png, icon-256.png, icon-512.png |
Icons at standard sizes |
The icons are the same green inbox graphic already used in the Tauri build — just copied to where the web server can reach them.
Then three additions to nuxt.config.ts:
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
{ rel: 'manifest', href: '/manifest.json' },
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
The apple-mobile-web-app-capable meta tag tells Safari this page is designed to run as a standalone app, not just a bookmarked webpage.
The result
After rebuilding the dashboard and re-adding it to the Dock (you do need to delete and re-create the Web App once), Bombay now:
- Shows the correct green inbox icon in the Dock, Finder, Launchpad, and Cmd+Tab
- Opens in its own window with no browser chrome
- Launches with a single click — no terminal needed
- Starts instantly because the backend service is already running via LaunchAgent
graph LR
A["Click icon in Dock"] --> B["Standalone window opens"]
B --> C["Connects to localhost:8765"]
C --> D["Dashboard ready"]
The whole thing is still a web page under the hood. But the experience is indistinguishable from a native app for daily use.
Why not just use Tauri?
Bombay does have a Tauri build that produces a proper .app with embedded Python services. That's the long-term distribution plan. But for local development, the Safari Web App approach has advantages:
- Zero build step. No
cargo tauri build, no PyInstaller, no signing. - Hot reload. Changes to the dashboard appear immediately in dev mode.
- The services already run. The LaunchAgent handles
project-watcher-service.pyat login. No sidecar management needed.
For a tool only I use on my own machine, the Safari Web App is the simpler path to "feels like a Mac app."
How to do this for your own project
If you have a local web app you want to dock-ify:
- Add a
manifest.jsonto your public directory with"display": "standalone"and icon references - Add
<link rel="apple-touch-icon">and<link rel="manifest">to your HTML head - Add
<meta name="apple-mobile-web-app-capable" content="yes"> - In Safari, visit your app and choose File → Add to Dock
- Make sure your backend starts at login (a LaunchAgent on macOS, systemd on Linux)
That's it. Five minutes of work for a noticeably better daily experience.