Skip to content

feat(dashboard): serve headless on a port and route annotate to the running daemon #428

Description

@null-hype

Problem

Today, show --port=<N> starts the dashboard HTTP server but skips acquireSingleton and startApp. This means a port-mode daemon:

  • Serves the UI over HTTP, but never installs the unix-socket connection handler
  • Cannot receive kill, reveal, or annotate requests from CLI clients
  • Doesn't participate in singleton ownership — a second show can spawn a competing instance

Separately, show --annotate calls runInSession(...), which opens a second, separate dashboard (openDashboardForContext → its own window) instead of routing to the one the user already has open.

Proposed solution

  1. Unify port and windowed modes through the singleton. Delete the port short-circuit in openDashboardApp; route both modes through acquireSingleton + startApp. Add a serveDashboardHeadless opener (no Chromium window) so --port serves the dashboard over HTTP while owning the singleton socket.

  2. startApp changes. Accept an open function parameter (defaults to innerOpenDashboardApp), return the dashboard state, and fire-and-forget reveal() instead of awaiting it — reveal awaits connectionLanded, which blocks indefinitely in headless mode when no browser tab is connected yet.

  3. Route show --annotate to the running daemon. Replace the runInSession detour with a spawn that dials the daemon's unix socket via --annotate, so annotation reuses the single open dashboard.

No new protocol, tokens, or HTTP endpoints — the existing unix socket (dashboardSocketPath()) is sufficient.

Prototype / fork

I have a working prototype as a patch-package patch against the built playwright-core bundle:

Branch: null-hype/playwright-cli@claude/dazzling-davinci-zq89f1

The branch includes:

  • patches/playwright-core+1.61.0-alpha-1781023400000.patch — the compiled JS patch
  • docs/upstream-headless-dashboard.md — the equivalent TypeScript source changes for an upstream PR to microsoft/playwright

Verified behavior

  • show --port=0 prints Listening on <url> + Waiting for dashboard connection + Dashboard is running pid=<N> and serves HTTP
  • A second show returns the same daemon pid (singleton ownership works)
  • show --annotate routes to the running daemon via the unix socket instead of spawning a second dashboard
  • First annotate blocks until a browser tab is connected (by design — nothing to annotate with nobody watching)

Typical workflow (headless / devcontainer)

# Terminal 1 — start the long-lived daemon
playwright-cli show --port=3000

# Browser — open http://localhost:3000

# Terminal 2 — agent uses playwright-cli normally; dashboard shows live sessions
# When the agent calls `show --annotate`, the review appears in the already-open tab

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions