Skip to content

feat: drop Tauri shell, ship axum-only web app#545

Merged
kwannoel merged 7 commits into
mainfrom
session-1-e78662
Apr 25, 2026
Merged

feat: drop Tauri shell, ship axum-only web app#545
kwannoel merged 7 commits into
mainfrom
session-1-e78662

Conversation

@kwannoel

Copy link
Copy Markdown
Owner

Summary

Removes the Tauri desktop target. The axum HTTP server is now the sole backend and the Svelte frontend talks to it via fetch + WebSocket.

Plan: docs/plans/2026-04-23-remove-desktop-app-design.md.

Pass 1 — browser-side native features (324fbe9)

  • html2canvas + new POST /api/save_screenshot route writes a temp PNG and returns the path Claude Code expects.
  • navigator.clipboard.write([new ClipboardItem(...)]) replaces the Tauri clipboard plugin for the terminal drag-drop image flow.
  • New helpers in src/lib/native.ts. Tauri branches kept temporarily so the desktop target still booted during this pass.
  • Cropped screenshots dropped (intentional — html2canvas captures the DOM only).

Pass 2 — strip @tauri-apps/* from the frontend (df76579)

  • src/lib/backend.ts is fetch/WebSocket only; isTauri deleted.
  • App.svelte always uses document.title for the window title.
  • Terminal.svelte drag-drop is now an unconditional DOM drop handler.
  • clipboard.ts uses navigator.clipboard.read() for image-presence checks.
  • package.json: @tauri-apps/{api,plugin-clipboard-manager,plugin-opener,cli} and the tauri script removed.

Pass 3 — delete the Rust Tauri layer (707419d)

  • src-tauri/server/; src-tauri/src/bin/server.rsserver/src/main.rs.
  • Crate renamed the-controllerthe-controller-server.
  • tauri, tauri-build, tauri-plugin-*, raw-window-handle, rfd dropped from Cargo.toml. axum + tower-http are now unconditional (no more server feature gate).
  • lib.rs reduced to module declarations; the old Tauri run() entry plus media.rs deleted.
  • auto_worker::AutoWorkerScheduler::start, maintainer::MaintainerScheduler::start, status_socket::start_listener now take Arc<AppState> instead of tauri::AppHandle. tauri::async_runtime::spawntokio::spawn.
  • main.rs boots the schedulers + cli_install + skills::sync_skills + status_socket listener that the Tauri setup() callback used to run.
  • All 59 #[tauri::command] wrappers + the media.rs module deleted; the *_impl functions stay and are what main.rs and tests call.
  • Dev harness: dev.sh boots the axum + vite pair; playwright.config.ts, e2e/eval.sh, CI workflow, and pre-commit hook all updated to point at server/.

Pass 4 — docs + cleanup (91b0baf, dd9ab58, 73c7047, 1925604)

  • docs/domain-knowledge.md rewritten for axum (spawn_blocking story now framed against tokio rather than the Tauri main thread; Tauri origin kept as historical note).
  • shell_env::parse_env_output fix: SKIP_VARS lines (PWD, _, SHLVL, OLDPWD) were being folded into the previous variable's value, so PORT=3001 ended up as \"3001\\nPWD=…\" and the server failed to bind. Regression test added.
  • Maintainer stopword list: \"tauri\"\"server\"; fixture paths and fingerprint string follow.
  • Test shims (scaffold_project, create_project, load_project, list_projects, get_auto_worker_queue, cancel_secure_env_request, submit_secure_env_value) and the unsafe state_from_ref transmute helper deleted; tests now call *_impl and crate::secure_env::* directly.
  • Stale tauri references removed from commands.rs / status_socket.rs / integration.rs / chat-mode.spec.ts.

Test plan

  • pnpm test — 300/300 passed
  • cd server && cargo test -- --test-threads=1 — 311/311 passed
  • cd server && cargo clippy --all-targets -- -D warnings — clean
  • cd server && cargo fmt --check — clean
  • pnpm check — 0 errors (16 pre-existing a11y warnings)
  • pnpm build — bundle builds cleanly
  • ./e2e/eval.sh \$PWD e2e/specs/smoke.spec.ts — passed
  • ./e2e/eval.sh \$PWD — 20 passed, 1 skipped, 1 pre-existing failure (merge-codex.spec.ts does not expand the seeded project before locating .session-label; identical spec on main)
  • Manual API verification: POST /api/save_screenshot with a 1×1 PNG data URL returns a valid PNG at <tmp>/the-controller-screenshot.png; three error cases return clean 400s.
  • Browser smoke test: html2canvas resolves, captureScreenshotPath() round-trips through /api/save_screenshot, and navigator.clipboard.write([new ClipboardItem({...})]) works under Chromium with clipboard permissions.

Notes

  • No more .dmg / .app distribution; users run ./dev.sh (or any other process manager) and open http://localhost:1420.
  • The only remaining tauri mentions are inside docs/plans/*.md design docs (historical, intentionally untouched).

🤖 Generated with Claude Code

kwannoel and others added 7 commits April 25, 2026 18:07
Pass 1 of desktop-app removal (see
docs/plans/2026-04-23-remove-desktop-app-design.md). Reimplements the
two native-only features that blocked dropping the Tauri shell:

- Screenshot: html2canvas renders the DOM; new /api/save_screenshot
  route decodes the base64 data URL and writes a PNG to the temp dir,
  keeping the existing file-path contract that downstream Claude Code
  CLI depends on.
- Clipboard image: DOM drop handler + navigator.clipboard.write
  replaces the Tauri drag-drop event + copy_image_file_to_clipboard.

Tauri branches are kept (gated on isTauri) so the desktop target still
works through Pass 1; Pass 2 will delete them. Cropping is dropped per
design decision — html2canvas captures the full DOM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass 2 of desktop-app removal. The web frontend no longer references
Tauri at all:

- backend.ts: drop isTauri, delete all Tauri branches in command,
  listen, listenAsync, openUrl — fetch/WebSocket only.
- App.svelte: captureScreenshot always uses captureScreenshotPath;
  updateWindowTitle always uses document.title; no more window plugin.
- Terminal.svelte: drop Tauri drag-drop listener; the DOM drop handler
  is now unconditional. isImageFile helper removed (callers gone).
- clipboard.ts: clipboardHasImage now uses navigator.clipboard.read()
  instead of the Tauri clipboard plugin.
- package.json: remove @tauri-apps/{api,plugin-clipboard-manager,
  plugin-opener,cli} and the tauri script; description updated.
- Test harness: vitest-setup no longer mocks isTauri; test files
  updated to mock document.title and $lib/backend openUrl instead of
  Tauri plugin mocks.

The src-tauri/ Rust crate is still buildable (Pass 3 deletes it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass 3 of desktop-app removal. The Tauri target is gone; the axum
server is now the sole backend.

Structural changes:
- src-tauri/ → server/ (directory rename)
- src-tauri/src/bin/server.rs → server/src/main.rs
  (promoted from optional --features server binary to the default bin)
- Crate renamed: the-controller → the-controller-server
- Dropped the `server` Cargo feature; axum + tower-http are now
  unconditional deps.
- Dropped tauri, tauri-build, tauri-plugin-opener,
  tauri-plugin-clipboard-manager, raw-window-handle, rfd.

Deleted files:
- src-tauri/src/lib.rs (Tauri run() entry point)
- src-tauri/src/main.rs (Tauri launcher)
- src-tauri/build.rs, tauri.conf.json, capabilities/, icons/
- src-tauri/src/commands/media.rs (screenshot + clipboard moved to browser)
- 59 #[tauri::command] wrappers across commands.rs, commands/kanban.rs
- Tauri emitter (kept NoopEmitter + WsBroadcastEmitter)

Refactored to take Arc<AppState> instead of tauri::AppHandle:
- auto_worker::AutoWorkerScheduler::start
- maintainer::MaintainerScheduler::start
- status_socket::start_listener + handle_connection + handle_cleanup
- tauri::async_runtime::spawn → tokio::spawn

main.rs now starts the background schedulers on boot
(cli_install, skills sync, status_socket listener, maintainer +
auto_worker) — previously lived in Tauri's setup() callback.

Tests: added thin test-only shims in commands.rs tests module for
the 6 deleted wrappers referenced by existing tests (list_projects,
create_project, load_project, scaffold_project, get_auto_worker_queue,
cancel_secure_env_request, submit_secure_env_value).
state_from_ref becomes an identity fn returning &AppState instead of
the tauri::State transmute hack.

Dev harness:
- dev.sh: boots axum+vite pair instead of tauri dev
- playwright.config.ts: cd server && cargo run --bin the-controller-server
- e2e/eval.sh: same
- CI workflow: server/ workspaces, dropped tauri system-dep installs
- pre-commit hook: cd server instead of cd src-tauri
- vite.config.ts: TAURI_DEV_HOST → DEV_HOST

Verification: pnpm test 300/300, cargo test 310/310, cargo clippy -D
warnings clean, cargo fmt clean, pnpm check 0 errors, pnpm build OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sweep docs/domain-knowledge.md after the Tauri removal: src-tauri/
paths become server/, lib.rs references become main.rs, the
"Synchronous Commands Block the Webview" section is rewritten for
axum (spawn_blocking on the tokio reactor) with the Tauri origin
kept as a historical note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before: SKIP_VARS lines (PWD, _, SHLVL, OLDPWD) emitted by
`/usr/bin/env` were never matched by the `is_valid_env_name &&
!SKIP_VARS` guard, so they fell through to the multi-line
continuation branch and got appended to the previous entry's value.
A child process launched with `PORT=3001` would end up seeing
`PORT="3001\n_=/usr/bin/env\nPWD=…"` after inherit_shell_env ran,
because the skipped `_=…` line was folded into the PORT value.

Fix: when a line parses as a valid KEY=VAL, flush the current
accumulator unconditionally. If KEY is in SKIP_VARS, don't record it
(and don't start a new accumulator) — but also don't treat the line
as a continuation. Added a regression test that reproduces the
PORT/_=/usr/bin/env scenario.

This was latent in the Tauri build too (GUI launches inherit launchd
env which doesn't normally contain PORT) but became load-bearing now
that the axum server reads PORT for binding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cosmetic cleanup after the Tauri removal:

- server/src/maintainer.rs: stopword list "tauri" → "server"; fixture
  paths and fingerprint string updated to match.
- server/src/commands.rs: drop stale "Tauri command wrapper" rationale
  from create_session_impl and stage_session_core docstrings.
- server/src/status_socket.rs: rephrase the lock-ordering note to
  reference axum command handlers, not Tauri commands.
- server/tests/integration.rs: rename "Tauri command level" comment to
  point at the *_impl request-handler functions.
- e2e/specs/chat-mode.spec.ts: drop the "future Tauri-native runner"
  comment and remove the test.fixme that was waiting on the daemon
  route — /api/read_daemon_token landed in commit 4a699ae.

The only remaining `tauri` mention is a comment in commands.rs tests
documenting why the test shims exist (they replace the deleted
#[tauri::command] wrappers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tests module had a set of pass-through wrappers (scaffold_project,
create_project, load_project, list_projects, get_auto_worker_queue,
cancel_secure_env_request, submit_secure_env_value) plus a
state_from_ref unsafe transmute that all existed only to preserve the
old #[tauri::command] call shape. With Tauri gone, the shims add no
value — they just hide which function is actually under test.

Replace every shim call site with a direct call to the underlying
function (scaffold_project_impl, secure_env::cancel_secure_env_request,
storage.list_projects, etc.) and delete the shims + state_from_ref.
The last `tauri` reference in the active codebase is gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kwannoel kwannoel enabled auto-merge (squash) April 25, 2026 10:09
@kwannoel kwannoel merged commit 6cb639e into main Apr 25, 2026
3 checks passed
@kwannoel kwannoel deleted the session-1-e78662 branch April 25, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant