Files
waggle-os/docs/audits/2026-07-08-desktop-wrapper-t14-analysis.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

194 lines
20 KiB
Markdown

# Desktop Wrapper T14 Analysis - 2026-07-08
Status: analysis supplement plus focused release-workflow, tray, packaged-startup, and sidecar hardening.
Purpose: deepen T14 evidence for the Tauri desktop wrapper, installer/update path, tray behavior, sidecar startup, and native-to-web UX bridge.
## Sources Inspected
- `app/src-tauri/tauri.conf.json`
- `app/src-tauri/capabilities/default.json`
- `app/src-tauri/src/lib.rs`
- `app/src-tauri/src/tray.rs`
- `app/src-tauri/src/service.rs`
- `app/src-tauri/Cargo.toml`
- `app/package.json`
- `app/tests/auto-update.test.ts`
- `app/scripts/*.test.ts`
- `apps/web/src/App.tsx`
- `apps/web/src/lib/tauri-bindings.ts`
- `apps/web/src/providers/ServiceProvider.tsx`
- `.github/workflows/release.yml`
- `scripts/check-sidecar-resources.mjs`
- `scripts/build-sidecar.mjs`
- `scripts/bundle-node.mjs`
- `scripts/bundle-native-deps.mjs`
- `scripts/stage-sidecar-deps.mjs`
Guideline baseline: Vercel Web Interface Guidelines, fetched 2026-07-08 from `https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md`.
## Commands Run
```powershell
npx tsc --noEmit --project app/tsconfig.json
npx vitest run app/tests/auto-update.test.ts app/scripts/signing-config.test.ts app/scripts/installer-config.test.ts app/scripts/bundle-runtimes.test.ts --reporter=dot
node scripts/check-sidecar-resources.mjs
cargo check --manifest-path app/src-tauri/Cargo.toml
npm run test -- tauri-bindings.test.ts adapter.tauri-branch.test.ts --reporter=dot
npx vitest run app/tests/e2e/startup.test.ts app/tests/e2e/chat.test.ts app/tests/e2e/workspaces.test.ts --reporter=dot
node -e "<parse tauri config/capability JSON files>"
app/src-tauri/resources/node.exe --version
npx vitest run packages/server/tests/tauri-config.test.ts --reporter=dot
npm run test -w apps/web -- src/lib/tauri-bindings.test.ts --reporter=dot
npx tsc --noEmit --project apps/web/tsconfig.json
npm run build:packages
npm run tauri:build:local
npx vitest run packages/server/tests/tauri-config.test.ts app/tests/auto-update.test.ts --reporter=dot
cargo test --manifest-path app/src-tauri/Cargo.toml service_script -- --nocapture
npx vitest run packages/server/tests/local/network-auth.test.ts --reporter=dot
node scripts/bundle-node.mjs
npx tauri build --debug --no-bundle
Start-Process app/src-tauri/target/debug/waggle.exe with isolated WAGGLE_DATA_DIR; poll http://127.0.0.1:3333/health
```
## Command Results
| Check | Result | UX meaning |
|---|---:|---|
| `app` TypeScript | Pass | Desktop build/signing/installer scripts typecheck. Refreshed in continuation run. |
| Tauri helper/static tests | Pass, 4 files / 85 tests | Auto-update config, signing config, installer config, and runtime-bundling helpers are covered. |
| Sidecar resource preflight | Pass | Local resources include Node runtime, native deps, and staged `node_modules`; `service.js` exists. Refreshed in continuation run. |
| Rust `cargo check` | Pass | Tauri Rust shell compiles in dev profile, including the tray native quit/settings changes. Refreshed after tray hardening. |
| Web-side Tauri binding tests from `apps/web` | Pass, focused binding 17/17 and prior binding/adapter bundle 19/19 | IPC command wrappers, adapter Tauri branches, the desktop navigation bridge, and native service/update notice mapping are covered when run through the web package. Output includes `punycode` deprecation warnings. Refreshed after service/update event surfacing. |
| Root Vitest command for web-side Tauri tests | Fails by command shape | Root Vitest excludes `apps/**`; this is a verification-documentation gap, not a product runtime failure. |
| App service E2E | Pass, 3 files / 11 tests | Service startup, health, settings persistence, chat SSE, workspace/session, and memory-scope API flows work in the Vitest harness. Output is noisy with mock-embedding and unknown-model cost warnings. |
| JSON parse | Pass | Tauri config, build/dev overrides, and capability JSON parse. |
| Bundled Node runtime | Pass | `app/src-tauri/resources/node.exe -p "process.version + ' abi=' + process.versions.modules"` returns `v22.22.2 abi=127` on this local build, matching the Node ABI that staged native deps. `bundle-node.mjs` now defaults to the current staging Node version, with `WAGGLE_BUNDLED_NODE_VERSION` available for an intentional pin. |
| Release workflow/tray/static desktop bridge guard | Pass, 1 file / 25 tests | `packages/server/tests/tauri-config.test.ts` now requires release builds to run `npm run build:packages` before each desktop sidecar bundle point, guards the tray menu against unsupported Pause/About/Quit-event actions, requires the web app to mount both desktop navigation and shell-event listeners, and guards updater-disabled startup plus Node ABI preflight behavior. |
| Web app TypeScript | Pass | `npm run typecheck:web` confirms the App-level desktop event bridge and Tauri binding types compile. |
| Updater-disabled static contract | Pass, 2 files / 35 tests | `tauri.conf.json` has updater config absent, updater capability is not exposed, and Rust no longer initializes `tauri_plugin_updater` while signed updater artifacts are missing. |
| Packaged debug binary build without installer | Pass | `npx tauri build --debug --no-bundle` builds `app/src-tauri/target/debug/waggle.exe` after sidecar/web resource preflight. |
| Full local debug bundle/MSI/NSIS | Pass locally | `npm run tauri:build:local --prefix app` completed and produced `Waggle_0.2.0_x64_en-US.msi` (150,135,110 bytes, 2026-07-10 18:59) and `Waggle_0.2.0_x64-setup.exe` (97,806,701 bytes, 2026-07-10 19:08). Both local debug artifacts are currently unsigned. |
| Packaged debug startup smoke | Pass | Launching `target/debug/waggle.exe` with an isolated `WAGGLE_DATA_DIR` reaches `/health` with `status: ok`, `mode: local`, `database.healthy: true`, `serviceHealth.watchdogRunning: true`; captured stderr contains no `CORS: origin not allowed`, `ERR_MODULE_NOT_FOUND`, or `NODE_MODULE_VERSION` errors. |
## Native Event Consumer Matrix
The tray-specific false affordances found in the first T14 pass are now narrowed:
Pause Agents and About Waggle are no longer offered from the tray, Settings has a
web bridge to the shipped `/settings` route, and Quit uses Tauri's native
`app.exit(0)` path. The updater is intentionally disabled until signed updater
artifacts exist, so update availability is future-ready in the web mapper but
not emitted by the native shell in the current build. Remaining active native
events are service-watchdog signals, not tray menu actions.
```text
waggle://navigate
waggle://service-restart-needed
waggle://service-status
```
| Native event / behavior | Source | Current consumer evidence | UX reading |
|---|---|---|---|
| Tray icon click / Open Waggle | `app/src-tauri/src/tray.rs` | Native show/focus; no React consumer needed | Source-wired, still needs packaged smoke. |
| Close window to tray | `app/src-tauri/src/lib.rs:135-140` | Native hide on close; no React consumer needed | Source-wired, still needs packaged smoke. |
| `Ctrl+Shift+W` global shortcut | `app/src-tauri/src/lib.rs` | Native toggle visibility; no React consumer needed | Source-wired, still needs target-OS smoke. |
| Tray Settings | `app/src-tauri/src/tray.rs`, `apps/web/src/App.tsx`, `apps/web/src/lib/tauri-bindings.ts` | Rust shows/focuses the main window, emits `waggle://navigate` to `/settings`, and the web app mounts `TauriDesktopEventBridge` to route only shipped desktop destinations. | Source-wired, still needs packaged smoke. |
| Tray Quit | `app/src-tauri/src/tray.rs` | Native `app.exit(0)` path; no React consumer needed. | Source-wired to Tauri `RunEvent::Exit`, still needs packaged smoke. |
| Pause Agents tray action | `app/src-tauri/src/tray.rs` | Menu item and emitter removed. | Hidden until there is real pause/resume behavior. |
| About Waggle tray action | `app/src-tauri/src/tray.rs`, `apps/web/src/App.tsx` | Menu item and `/about` emitter removed; no missing-route target remains. | Hidden until there is a real About destination. |
| `waggle://update-available` | `apps/web/src/lib/tauri-bindings.ts`, `apps/web/src/App.tsx` | `listenDesktopShellEvents()` keeps a future `Update available` toast mapper, but Rust updater registration/emission is disabled while updater signing is not provisioned. | Future-ready web mapping only; signed-update release-channel proof remains deferred. |
| `waggle://service-status` | `app/src-tauri/src/service.rs`, `apps/web/src/lib/tauri-bindings.ts`, `apps/web/src/App.tsx` | `restarting` maps to a reconnecting toast; `failed` maps to a destructive stopped-service recovery toast. | Source/unit-wired to visible UI, still needs packaged watchdog smoke. |
| `waggle://service-restart-needed` | `app/src-tauri/src/service.rs`, `apps/web/src/lib/tauri-bindings.ts`, `apps/web/src/App.tsx` | `listenDesktopShellEvents()` maps restart-needed events to a visible local-service restart toast. | Source/unit-wired to visible UI, still needs packaged watchdog smoke. |
Continuation refresh:
```text
rg -n -F "waggle://pause-agents" app/src-tauri apps/web/src -> no product emitter/listener
rg -n -F "waggle://navigate" app/src-tauri apps/web/src -> tray emitter + web bridge
rg -n -F "waggle://quit" app/src-tauri apps/web/src -> no product emitter/listener; tray uses app.exit(0)
rg -n -F "waggle://update-available" app/src-tauri apps/web/src -> web shell-event listener only; native updater emission disabled
rg -n -F "waggle://service-status" app/src-tauri apps/web/src -> Rust emitters + web shell-event listener
rg -n -F "waggle://service-restart-needed" app/src-tauri apps/web/src -> Rust emitter + web shell-event listener
```
Inference: Settings now has the frontend listener required for its delegated route
behavior, Quit no longer delegates to React, service-watchdog signals now surface
as visible toasts through the same desktop bridge, and update availability has a
future-ready web mapper while native updater emission remains disabled. The
packaged startup/smoke layer is now proven for app boot and sidecar health; tray,
close-to-tray, shortcut, and forced watchdog-restart interactions still need
targeted packaged interaction evidence.
## What Is Proven Now
- Static desktop TypeScript, Rust compilation, config JSON, resource staging, and service-level API flows are in good shape.
- The active installed sidecar path is `packages/server/src/local/service.ts` -> `scripts/build-sidecar.mjs` -> `app/src-tauri/resources/service.js` -> `app/src-tauri/src/service.rs`.
- `scripts/check-sidecar-resources.mjs` correctly prevents a raw Tauri build from silently omitting staged runtime resources.
- `ServiceProvider` has a generic boot/reconnect path with three retries and broadcasts connect-settled state; several routed surfaces show "service unreachable" states.
- Release workflow builds workspace packages, then stages Node, native deps, sidecar deps, and frontend before Tauri action builds Windows/macOS draft release artifacts.
- Native tray click/Open, close-to-tray, and the global shortcut are implemented in Rust rather than delegated to missing web listeners.
- Tray Settings is source-wired through a Tauri desktop navigation bridge to `/settings`; unsupported `/about` navigation is no longer emitted.
- Tray Quit is source-wired through Tauri `app.exit(0)`, so the existing `RunEvent::Exit` sidecar cleanup path is reachable from the menu.
- Pause Agents and About Waggle are no longer exposed as tray actions until there is real product behavior behind them.
- Native service-watchdog events now have React consumers, and the future update event mapper is ready: `waggle://update-available`, `waggle://service-status`, and `waggle://service-restart-needed` map to concise toasts, guarded by `tauri-bindings.test.ts` and the static desktop bridge test.
- Packaged debug startup now works locally: the Tauri shell creates the tray icon, starts the bundled sidecar from `target/debug/resources/service.js`, reaches `/health`, and reports healthy database plus running watchdog.
- Startup blockers found and fixed by packaged smoke: invalid `plugins.dialog` config, updater plugin initialization without updater config, debug-sidecar source path resolution, bundled Node/native ABI mismatch, and Tauri webview `http://tauri.localhost` CORS rejection.
## Still Not Proven
- Packaged debug app launch and sidecar health on Windows are proven locally; clean installed MSI/NSIS launch on Windows/macOS remains unproven.
- Published release artifact availability and signed-installer trust are still not proved; the local debug MSI/NSIS artifacts are present but `Get-AuthenticodeSignature` reports `NotSigned` for both.
- Actual packaged tray menu behavior for Open, Settings, and Quit.
- Close-to-tray behavior in the installed binary.
- `Ctrl+Shift+W` global shortcut behavior on target OS.
- Packaged-app proof that forced watchdog events produce the expected visible toasts.
- Port-conflict recovery in the installed app.
- Installer warning/trust experience for unsigned, self-signed, or properly signed channels.
- Auto-update user experience. The updater config, capability, and Rust runtime registration are intentionally disabled for v1 until signed updater artifacts exist; the web mapper remains future-ready.
## Line-Level Findings
| ID | Finding | Evidence | Correction |
|---|---|---|---|
| T14-1 | Service app-level events previously had no React consumers, and update mapping was not future-ready. | Current `tauri-bindings.ts` maps `waggle://update-available`, `waggle://service-status`, and `waggle://service-restart-needed` to toast notices, and `App.tsx` mounts `listenDesktopShellEvents()` through `TauriDesktopEventBridge`; guarded by focused binding tests and `tauri-config.test.ts`. Native update emission remains disabled until signed updater artifacts exist. | Focused fixed locally for service events and future update mapping; packaged smoke still needs to prove real service-watchdog events produce visible toasts. |
| T14-2 | About tray action previously targeted a route that does not exist. | Current `tray.rs` no longer includes `About Waggle` or `/about`; guarded by `tauri-config.test.ts`. | Focused fixed locally by removing the unsupported tray action. |
| T14-3 | Quit tray action previously delegated to an unconsumed web event. | Current `tray.rs` uses `app.exit(0)`; guarded by `tauri-config.test.ts` and `cargo check`. | Focused fixed locally; packaged smoke still needs to prove sidecar cleanup through the real menu. |
| T14-4 | Update UX is intentionally disabled and must not break startup. | `app/tests/auto-update.test.ts` and `packages/server/tests/tauri-config.test.ts` require updater config absent, updater capability not exposed, no `UpdaterExt` import, no updater plugin registration, and no startup `.updater()` check. Packaged smoke originally panicked on `plugins.updater: null`; the current binary no longer does. | Keep update UI hidden/deferred, or re-enable signed updater artifacts and visible update handling end to end. |
| T14-5 | Generic service reconnect exists, and native watchdog events now surface through the desktop bridge. | `ServiceProvider.tsx` still owns generic reconnect, while `listenDesktopShellEvents()` turns native watchdog status/restart events into visible recovery toasts. | Focused fixed locally; installed-app watchdog failure/restart proof remains. |
| T14-6 | Web-side Tauri tests are not discoverable from the root Vitest command. | Root command exits "No test files found" because root config excludes `apps/**`; running from `apps/web` passes 2 files / 19 tests. | Document the correct web-package command in the final verification lane or align root test discovery. |
| T14-7 | Installed-app UX still lacks real rendered evidence. | Current command evidence is source/static/API-level only. | Capture packaged app startup, tray, close-to-tray, shortcut, service recovery, and installer trust evidence before a full 9/10 claim. |
| T14-8 | Release packaging previously skipped the package build step used by the PR Tauri verification lane. | Historical `.github/workflows/release.yml` went from `npm install` directly to sidecar bundling; current workflow runs `npm run build:packages` before both Windows and macOS sidecar bundle steps, guarded by `tauri-config.test.ts`. | Focused fixed locally; full release closure still needs signed/public artifacts and installed-app smoke. |
| T14-9 | Settings tray action previously emitted an unconsumed route event. | Current `App.tsx` mounts `TauriDesktopEventBridge`; `tauri-bindings.ts` listens for `waggle://navigate` and accepts only `/settings`; focused web binding tests pass 15/15. | Focused fixed locally; packaged smoke still needs to prove the real tray menu reaches Settings. |
| T14-10 | Packaged startup previously panicked before the UI could load. | Direct debug-exe smoke exposed `plugins.dialog` object deserialization and updater `null` deserialization panics. Current config removes `plugins.dialog`; current Rust does not register updater while config is absent; static tests guard both. | Fixed for debug packaged startup; installer artifact smoke remains. |
| T14-11 | Packaged sidecar previously launched the dev `service.ts` path from the wrong root. | Smoke exposed `ERR_MODULE_NOT_FOUND` for `D:\packages\server\src\local\service.ts`. `service.rs` now prefers bundled `resources/service.js` when present, falls back to ancestor-searched dev source only when needed, and has Rust unit tests for both paths. | Fixed; packaged startup smoke reaches `/health`. |
| T14-12 | Bundled Node and staged native deps could silently have incompatible ABIs. | Smoke exposed `better_sqlite3.node` built for ABI 127 running under bundled Node ABI 115. `bundle-node.mjs` now defaults to the current staging Node version; `check-sidecar-resources.mjs` fails on ABI mismatch before packaging. | Fixed locally; CI remains aligned because CI stages under Node 20 unless intentionally changed. |
| T14-13 | Tauri webview health calls could be rejected by local CORS. | Packaged logs showed `CORS: origin not allowed` before allowing `http://tauri.localhost`; `network-auth.test.ts` now covers Tauri webview localhost origins. | Fixed; current packaged smoke has no CORS rejection in stderr. |
## Correction Decision
Do not treat all tray items equally:
1. Keep Rust-native handling for Open, close-to-tray, and `Ctrl+Shift+W`, then verify them in a packaged smoke.
2. Keep Settings as the only delegated tray route and route it through the tested desktop navigation bridge.
3. Keep Quit native through `app.exit(0)` so sidecar cleanup is reachable.
4. Keep Pause Agents and About Waggle hidden until they have real product behavior.
5. Keep service watchdog events surfaced through accessible toasts, keep update mapping future-ready while updater is disabled, then prove watchdog behavior in a packaged smoke.
## T14 Acceptance
T14 remains open until either:
1. Desktop wrapper/release UX is explicitly deferred from the five-persona score, or
2. Evidence proves all of the following:
- Packaged app launches and reaches Home or a clear service-recovery screen. Current debug smoke proves sidecar `/health`; rendered Home still needs packaged visual proof.
- Tray Open, Settings, and Quit actions are proved in a packaged smoke; Pause and About remain intentionally removed until implemented.
- Close-to-tray and `Ctrl+Shift+W` are verified on a target OS lane.
- Sidecar startup, port conflict, crash/restart, and restart-needed states are visible and recoverable.
- Installer/signing expectations are documented for the actual release channel.
- Update UX is either fully signed and user-visible or intentionally disabled without misleading UI.
## Phase Impact
This does not change Phase 1. T14 remains a Phase 2/Launch final-product gate after in-app P0 blockers are cleared, unless a Phase 1 verification command directly requires a small supporting fix.