ReviewOS

also looking at this

craft-native/craft

feat(cli): --headless, and make DevTools reachable at all

#61
Merged glennmichael123 wants to merge feat/headless into main
9 files +340 -6
docs/BRIDGE_API.mdmodified+57-0
Changes to docs/BRIDGE_API.md
@@ -10,6 +10,7 @@ The Craft JavaScript Bridge provides a seamless interface for your web applicati
1010- [Application Menu (macOS)](#application-menu-macos)
1111- [Global Shortcuts (macOS)](#global-shortcuts-macos)
1212- [Settings and Preferences (macOS)](#settings-and-preferences-macos)
13- [Headless mode](#headless-mode)
1314- [Window API](#window-api)
1415- [App API](#app-api)
1516- [TypeScript Support](#typescript-support)
@@ -464,6 +465,61 @@ const { domain, readCommand } = await craft.prefs.info()
464465craft namespaces its keys, so `clear()` and `keys()` cannot disturb the window
465466positions and inspector state AppKit and WebKit keep in the same domain.
466467
468## Headless mode
469
470`craft --headless` builds the window and never puts it on screen. The page
471loads, JavaScript runs, and the webview stays capturable — a snapshot taken
472after script has mutated the DOM reflects the mutation.
473
474```bash
475craft http://localhost:3000 --headless --timing
476```
477
478Also `headless: true` in `craft.json`, and `window.headless` on `AppConfig`.
479
480The motivating case is measurement: a perf loop that launches craft repeatedly
481no longer flashes a window at the operator each time, which is worth doing on
482its own terms.
483
484### What it cannot do
485
486**It does not animate.** An unshown window does not drive the compositor, so
487`requestAnimationFrame` stops after the first frame and page timers fall to
488roughly 1Hz. That means, silently:
489
490- CSS and JavaScript animations stay on frame one
491- canvas and WebGL render loops never advance
492- charting libraries that redraw on rAF show their initial state
493- "wait until the spinner stops" — implemented with `setInterval` — crawls
494
495Drive the page with explicit calls, and take readiness from load completion
496rather than from a polling loop inside the page. No invisible window
497configuration avoids this: what drives the compositor is the window being
498*unoccluded*, which needs activation, which means stealing focus and showing a
499Dock icon — a worse trade for the operator this mode exists to help.
500
501`--headless` and `--menubar-only` are contradictory (a menubar app has no
502window to hide) and passing both is an error rather than a silent no-op.
503
504### Inspecting a running window
505
506`craft --dev-tools` enables the Web Inspector and makes the webview appear in
507Safari's **Develop** menu.
508
509This flag is new because there was previously no way to turn DevTools *on*:
510the setting defaulted to off and the only flag, `--no-devtools`, set it off
511again. And even when it was enabled through `craft.json`, the inspector did not
512work on macOS 13.3 or later, which introduced `isInspectable` defaulting to
513`NO` — craft set only the older `developerExtrasEnabled`, which stopped being
514sufficient.
515
516It is a **debugging affordance, not automation**. It opens no port and prints
517no endpoint, because there is nothing to print: WebKit on macOS has no
518socket-based inspector server — an inspectable `WKWebView` holds no internet
519sockets at all — and `safaridriver` refuses to attach to anything that is not
520Safari. Driving a craft page from another process needs a control channel craft
521owns, which does not exist yet.
522
467523## Window API
468524
469525Control the application window from JavaScript.
@@ -697,6 +753,7 @@ async function onComplete() {
697753| Tray Menu | �� | �� | �� |
698754| Window Control | ✅ | ✅ | ✅ |
699755| Hide Dock Icon | ✅ | ➖ | ➖ |
756| Headless | ✅ | �� | �� |
700757| Global Shortcuts | ✅ | �� | �� |
701758| Settings… item | ✅ | �� | �� |
702759| Preferences | ✅ | �� | �� |