Skip to content

[CI/CD] Smoke-test the packaged desktop launch, health, and native bridge #232

Description

@fettpl

Audit metadata

  • Priority: P1
  • Estimated effort: M
  • Implementation risk: MED
  • Category: tests
  • Evidence baseline: 07be5be7ce69bea0c3118744ab90d148b010fce0 (origin/main on 2026-07-17)

Dependencies

  • P1 proposal 001 — Gate every release on the exact commit that passed CI. Included in the current P1 issue wave.
  • P1 proposal 017 — Make the CI and release toolchain immutable and reproducible. Included in the current P1 issue wave.

Description and impact

The existing desktop post-pack check proves that one embedded server file was
copied, but it does not launch the packaged app. Packaging can still ship an app
whose embedded Next server fails, whose native PTY module cannot load, or whose
health endpoint never becomes reachable. A bounded end-to-end smoke on the
unpacked production bundle catches those failures before release assets are
created.

Current state

  • frontend/package.json:29-38 defines packaged desktop:pack and release
    desktop:dist builds.
  • frontend/desktop/electron-builder.yml:7-58 selects app files, native PTY
    modules, embedded standalone resources, targets, and afterPack.
  • frontend/scripts/electron-builder-after-pack.mjs:30-50 asserts only that a
    nested or flat server.js exists.
  • frontend/desktop/logic/app-server.ts:38-64 persists the embedded frontend
    port in embedded-frontend.port; startup waits for the server before showing
    the app.
  • frontend/desktop/preload.ts:18-24 exposes localStudioDesktop.terminal.status;
    that IPC call loads @lydell/node-pty and reports availability.
  • Desktop startup supports an isolated user-data directory and updater-disable
    environment setting; use both so CI cannot touch developer state or update.
  • Current CI never launches an electron-builder output.

Verification commands

Purpose Command Expected on success
Build unpacked app npm --prefix frontend run desktop:pack platform unpacked app exists
Smoke npm --prefix frontend run desktop:smoke health and PTY bridge pass; child exits
Frontend gate npm --prefix frontend run check:quality exit 0
Full gate npm run check && npm run test:integration all pass
Release bundle npm --prefix frontend run desktop:dist production artifacts created

Scope

In scope:

  • frontend/scripts/desktop-package-smoke.mjs (create)
  • frontend/scripts/desktop-package-smoke.test.mjs (create)
  • frontend/package.json
  • frontend/package-lock.json only if a test dependency is unavoidable
  • .github/workflows/ci.yml
  • frontend/desktop/electron-builder.yml only to expose a deterministic
    unpacked output path or required environment, not to change release identity

Out of scope:

  • UI automation beyond startup and bridge health.
  • Code signing/notarization validation or publishing release assets.
  • Using a developer's canonical user data, controller, or model installation.
  • Replacing the existing afterPack guard.
  • Cross-platform matrix beyond the macOS runner currently used for release; add
    other platforms only when packaging support exists.

Solution design

Create a dependency-free Node harness that resolves the current platform's
unpacked executable, creates a temporary user-data directory, allocates a CDP
debug port, and spawns the packaged app with auto-update disabled and all
runtime/data paths isolated. Wait for embedded-frontend.port, request
http://127.0.0.1:<port>/api/desktop-health, and require HTTP 200 plus the
documented healthy JSON. Connect to the BrowserWindow through CDP and evaluate
window.localStudioDesktop.terminal.status(); require available: true to prove
the packaged native module loads. Quit gracefully, escalate termination only
after a timeout, verify no app/embedded-server child remains, and always remove
temporary state.

Implementation plan

Step 1: Build a unit-tested lifecycle harness

Create focused pure helpers for output-path resolution, bounded file polling,
health validation, CDP target selection, and child cleanup. Inject spawn, fetch,
clock, and filesystem dependencies in unit tests. Do not add comments; use named
functions to express intent.

Verify: node --test frontend/scripts/desktop-package-smoke.test.mjs → success, timeout, malformed health, early exit, and cleanup cases pass.

Step 2: Launch the real packaged app in isolation

Implement desktop-package-smoke.mjs with a unique temporary user-data path,
LOCAL_STUDIO_DESKTOP_DISABLE_AUTO_UPDATE=true, no dev-server URL, an isolated
agent cwd/data directory, and a hard global deadline. Capture bounded stdout and
stderr for failure diagnostics without printing environment variables.

Verify: npm --prefix frontend run desktop:pack && npm --prefix frontend run desktop:smoke → exits 0 and removes its temporary directory and child processes.

Step 3: Assert embedded health and the native PTY bridge

Read the persisted port only from the temporary user data. Validate the desktop
health payload, then use CDP Runtime.evaluate with awaited promises to call the
preload terminal status API. Fail if the bridge is absent, rejects, or reports
the native module unavailable. Do not open a shell in CI.

Verify: deliberately point the harness at a copied bundle missing the native PTY artifact in a unit fixture → smoke fails with a redacted actionable error; the normal bundle passes.

Step 4: Add a macOS CI packaging gate

Add a macOS job to .github/workflows/ci.yml after source gates. Build the
unpacked app for the runner architecture and run desktop:smoke. Preserve Plan
001 release dependencies so release waits for this job; preserve audit proposal 017 pins
and frozen installs. Upload bounded logs only on failure, never user data.

Verify: workflow pin validation passes and the release job's needs includes the new desktop smoke job.

Step 5: Run full validation and canonical reinstall

Run tests and both desktop build modes, then use the exact AGENTS.md sequence
to replace /Applications/Local Studio.app, relaunch, and query its recorded
/api/desktop-health endpoint.

Verify: node --test frontend/scripts/desktop-package-smoke.test.mjs && npm --prefix frontend run desktop:pack && npm --prefix frontend run desktop:smoke && npm --prefix frontend run check:quality && npm run check && npm run test:integration && npm --prefix frontend run desktop:dist → all exit 0; installed desktop health returns HTTP 200 with ok: true.

Test plan

  • Unit lifecycle: successful startup, child exits before port, missing port,
    timeout, malformed port, non-200 health, invalid JSON, unhealthy JSON, missing
    preload bridge, unavailable PTY, and cleanup after every failure.
  • Real unpacked app: embedded standalone server, static assets, desktop health,
    preload bridge, and packaged native PTY load.
  • Assert temporary data/log paths are removed and no recorded PID remains alive.
  • Ensure smoke never contacts update servers or a production controller.

Acceptance criteria

  • desktop:smoke launches the actual unpacked production app.
  • State, cwd, update behavior, and ports are isolated and bounded.
  • Embedded /api/desktop-health returns 200 and healthy JSON.
  • Packaged preload IPC loads the native PTY module successfully.
  • Success and all tested failures clean up every child and temp path.
  • CI runs the smoke before the exact-SHA release job.
  • Full gates pass and canonical installed desktop health returns 200.

Risks and stop conditions

Stop and report if:

  • The app lacks a supported user-data override or still touches canonical state.
  • CDP cannot be enabled only for the smoke process without changing production
    security defaults.
  • Native PTY availability cannot be queried without opening an interactive shell.
  • CI architecture cannot produce a runnable unpacked app.
  • A release would need to be published to test the flow.

Maintenance considerations

Keep the smoke below a fixed timeout and preserve failure logs as bounded
artifacts. Reviewers should verify it launches dist-desktop, not electron .
or a dev server. Any new packaged native dependency should gain a bridge-health
assertion in this harness.


Generated from Local Studio 2.0 main-branch audit proposal 030.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions