Add the view shell and cmux preview open: the UI in real windows without the app

Merged
TE
2d ago
+866-34

What

This adds cmux preview open and the view shell, Prototypes/ViewShell: cmux NEXT's UI in real, draggable, resizable macOS windows, without the app. It has no terminals, Ghostty, daemons, Cloud or Chief.

Leo (2026-11-04): "a dragabble window that is legit the app through and through without the heavy bits", and "can't you literally just ship like a bare bones version of the app ... you literally just ship the shell".

cmux preview open --size 1512x982 --size 1280x800 --size 900x700 --script streaming
cmux preview open --fixture app.window/new-tab --density compact --capture /tmp/shots --quit

The model is Expo Go: the shell is native and is built rarely, and everything else is web, loaded live.

  • The window is an NSWindow with the system titlebar, traffic lights and an NSVisualEffectView (.sidebar, behind-window) under a clear WKWebView. It opens one window per --size, offset so all of them show.
  • The content is the webviews mock app (gallery/app.html, the web PR) with chrome=native. The page draws no window frame, fills the web view and re-lays its panes out on resize. Each --KEY VALUE becomes a mock app URL key (--fixture, --script, --density, --theme, --cpu, --slot, and so on), so new conditions never need a native build.
  • Hot reload. By default the shell loads the gallery dev server on 127.0.0.1:4210. launch.sh starts it when nothing answers and stops only the server it started. Vite HMR and the gallery's per-entry reload update every open window when a web file is saved, with no restart. --dist DIR serves a built gallery through a cmux-preview:// scheme handler instead, so nothing listens on a port.
  • Your look. The shell reads appearance.theme from ~/.config/cmux/cmux-next.json (a light/dark pair takes the system's side) and appearance.accent from the same file. You can also point it at another file with CMUX_NEXT_CONFIG_FILE. A flag on the command line wins.
  • Captures for the UI judge. --capture DIR writes DIR/<W>x<H>.png per window once the page posts ready (webkit.messageHandlers.cmuxPreview). --quit exits after the last capture. Each window prints ready_ms= on stdout.
  • Size. The shell is four Swift files with no dependency on any app package. swift build -c release takes seconds and runs again only when this folder changes.
  • Live code (Swift hot reload). In Debug the shell is linked -interposable. inject.sh --live DIR FILE.swift compiles the edited file against the build and links it into a patch dylib. The running shell (--live DIR) loads the patch, interposes every Swift function it shares with the executable (dyld_dynamic_interpose) and redraws, with no relaunch. The titlebar badge (Badge.swift) is the demo view: its body is an AnyView, so a patch may change what it builds. --headless --live-render DIR runs the same loop with no screen and writes each generation's picture. scripts/measure/live-code-shell.sh measures it.
  • The CLI. cmux preview open joins cmux dev ui-host in one launcher table in cli/dev.rs. Unlike dev ui-host, it is not dev-only. The tests cover both.

This depends on the web PR (#35312) for app.html and chrome=native. The shell builds without that PR, but its windows have nothing to load until it lands.

How it differs from the gallery: the gallery shows one entry's states, each alone in a frame in a browser. The shell shows the whole app, one window per state, at real sizes, in real windows.

Not in this PR:

  • appearance.background (the person's picture behind the glass).
  • The read-only attach to the person's real acpmux. The mock host is the only model today.
  • The hot-swap and first-frame numbers on a real display. The next step is a capture-mini run, with the result posted on this PR.

Testing

  • cargo test -p cmux-tui --lib -- cli::dev passed on the fleet.

  • swift build -c release --package-path Prototypes/ViewShell passed on a fleet Mac in 7.6 s.

  • Live code on a fleet Mac (scripts/measure/live-code-shell.sh, headless), at the latest head:

    • Debug build: 10.0 s.
    • Patch load: 2 ms (4 functions interposed).
    • Edit to redrawn pixels: 4.5 s for the first patch and 2.0 s for the next patch of the same file. The second patch draws a different picture from the first.
    • A stored-property edit is applied as well, and flagged unsafe because the layout changed. The loader does not refuse it, so telling the user that such a change needs a rebuild is up to the tool that sends the patch.

Observed / Not run

  • Observed: generation-1, -2 and -3 pictures written after each patch, with no relaunch. Both launcher tests passed (forwards_launcher_arguments, only_curated_surface_handles_ui_layer_command), and the release build compiled.
  • Not run: opening the windows on a display, the capture PNGs, ready_ms on a Mac and a hot-reload save while windows are open. These need a display worker, and I have not run them yet.

No capture: the windows come from the web PR's page. Its numbers are in that PR. A real-window capture follows as a comment once a display worker runs it.

Changelog

none

šŸ¤– Generated with Claude Code

Agent session: agent session