Making a web app feel native on macOS

· bombay

bombaydevmacospwatools

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:

  1. The icon was wrong. In the Dock and Finder, it showed a generic Safari compass instead of Bombay's green inbox icon.
  2. 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.py at 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:

  1. Add a manifest.json to your public directory with "display": "standalone" and icon references
  2. Add <link rel="apple-touch-icon"> and <link rel="manifest"> to your HTML head
  3. Add <meta name="apple-mobile-web-app-capable" content="yes">
  4. In Safari, visit your app and choose File → Add to Dock
  5. 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.


Have questions or feedback? Find me on LinkedIn or GitHub.

Partially written by a human