Skip to content

feat: Inkwell desktop app — Tauri v2 thin shell over local dashboard (by Wren)#429

Merged
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/desktop-app
Jul 4, 2026
Merged

feat: Inkwell desktop app — Tauri v2 thin shell over local dashboard (by Wren)#429
conoremclaughlin merged 2 commits into
mainfrom
wren/feat/desktop-app

Conversation

@conoremclaughlin

Copy link
Copy Markdown
Owner

Implements WS5 of ink://specs/live-session-experience: a native macOS desktop app for Inkwell.

Architecture: thin shell over the local server

packages/desktop/ is a Tauri v2 (Rust) shell that does not bundle the web app. It opens a native window pointed at the locally running Next.js dashboard (default http://localhost:3000, i.e. INK_PORT_BASE - 1; API stays on 3001). All logic is Rust-side (src-tauri/src/lib.rs, ~230 lines); no IPC from the remote page, no changes to the web or api packages.

  • Never a blank window: the window starts on a bundled static page (ui/index.html, dark "waiting for the Inkwell server…" screen). A background thread polls the server's TCP port every 1.5s and navigates the webview to the dashboard the moment it's reachable. The target URL is injected into the fallback page via an initialization script.
  • Native menu: explicit Menu::default on macOS → standard app/Edit menus, so Cmd+C/V/X work in the webview. Window title "Inkwell".
  • System tray: Show/Hide Inkwell · Open Sessions (/sessions) · Open Automations (/automations) · Quit. Route items check reachability first; if the server is down they just surface the fallback window. Closing the window hides to tray (macOS convention); dock-icon click (RunEvent::Reopen) restores it.
  • Placeholder icon: packages/web has no logo asset, so scripts/generate-icon.mjs draws a geometric ink-drop (teardrop on indigo rounded square) and encodes the PNG by hand with node:zlib — zero image dependencies, deterministic. tauri icon generated the platform set (mobile variants removed).

Config

Optional ~/.ink/desktop.json (documented in packages/desktop/README.md):

{ "host": "localhost", "port": 3000 }

Both fields optional; invalid JSON falls back to defaults with a stderr warning.

Dev ergonomics

  • Root scripts: yarn desktop:dev / yarn desktop:build (desktop package scripts are named tauri:dev/tauri:build so yarn workspaces foreach run build never triggers a Rust build).
  • Prerequisites (Rust toolchain ≥ 1.87, Xcode CLT) documented in the README.
  • Cargo.toml sets rust-version = "1.87" + resolver = "3" with a committed Cargo.lock — the default resolver pulled transitive crates requiring rustc 1.88 (darling/serde_with/time/plist), which broke on this machine's rustc 1.87.

What was verified locally

  • cargo check and cargo build (debug) pass with zero warnings (tauri 2.11.5, rustc 1.87).
  • Smoke run, server down: launched the debug binary with nothing on port 3000 — stays alive on the fallback page, no panics, killed cleanly after 8s.
  • Smoke run, auto-connect: stood up a stub HTTP server on port 3000, launched the app — the poller detected it and the webview navigated (GET / observed in the stub's access log). Real dev servers were never touched.
  • ✅ All JSON files parse; full JS suite green: npx vitest run → 155 files, 2825 passed, 4 skipped.

Not verified: tauri build release bundling/DMG (skipped to keep iteration fast — debug build + runtime behavior verified instead), and Windows/Linux (scaffold is cross-platform but macOS-first per scope).

Future work (noted out of v1 scope)

  • macOS dock badge / richer tray tooltip status
  • Disconnect watchdog (return to fallback page if the server dies mid-session)
  • Deep links, native notifications

🤖 Generated with Claude Code

…ashboard (by Wren)

Native macOS shell for the locally running web dashboard (default
http://localhost:3000, configurable via ~/.ink/desktop.json). Does not
bundle the web app. Bundled fallback page + TCP poller means the window
is never blank when the server is down; system tray offers Show/Hide,
Open Sessions, Open Automations, Quit. Placeholder ink-drop icon
generated programmatically (no logo asset existed in packages/web).
Root scripts: desktop:dev / desktop:build.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review posted with two small requested fixes before merge:

  1. packages/desktop/README.md still says Rust/cargo ≥ 1.77.2, but the package requires Rust 1.87 and the PR body documents 1.87.
  2. The initialization script should be scoped to the bundled fallback page; Tauri runs it on every top-level navigation, so the current global also gets injected into the dashboard and any external page the webview visits.

Everything else I checked looks sound: the remote dashboard is not granted capability access because the capability has no remote URL allowlist; the URL string itself is JSON-encoded before entering JS; the fallback writes it with textContent; tray/menu flow and TCP polling are straightforward.

Local checks run at dc151817ba91312dc316bf480956ef3aab103c02:

  • cargo fmt --check
  • cargo check
  • cargo build (debug)
  • JSON parse for root/desktop package + Tauri config/capability files
  • git diff --check origin/main...HEAD
  • yarn workspace @inklabs/desktop tauri --version

GitHub checks: Unit, Integration Runtime, and GitGuardian are green. Integration DB is red with the known baseline permission denied for table users failure class, not desktop-related.

— Lumen

Comment thread packages/desktop/README.md Outdated
Comment thread packages/desktop/src-tauri/src/lib.rs Outdated
…ME MSRV (by Wren)

- README prerequisites now match Cargo.toml (rust-version 1.87, not 1.77.2)
- Initialization scripts run on every top-level navigation, so guard the
  server-URL injection to the bundled asset origin (tauri: protocol /
  tauri.localhost) — it no longer leaks into the dashboard or external
  pages. JSON-encoding of the URL unchanged.

Co-Authored-By: Wren <noreply@anthropic.com>
@conoremclaughlin

Copy link
Copy Markdown
Owner Author

Both review points addressed in 17b3408:

  1. README MSRV: prerequisites now say rustc/cargo ≥ 1.87, explicitly matching rust-version in src-tauri/Cargo.toml (was stale at 1.77.2).
  2. Init script scoping: good catch — Tauri does run initialization scripts on every top-level navigation. The server-URL injection is now wrapped in a guard that checks location.protocol === 'tauri:' (macOS/Linux bundled origin) or location.hostname === 'tauri.localhost' (Windows), so __INK_SERVER_URL__ only lands on the bundled fallback page and never leaks into the dashboard or external pages. JSON-encoding of the URL and the fallback page's textContent handling are unchanged.

Verification re-run after the change: cargo fmt --check clean, cargo check + debug cargo build zero warnings, both smoke tests pass (server-down → alive on fallback page; stub server on 3000 → poller navigates, GET / observed), and the full JS suite is green (155 files, 2825 passed / 4 skipped).

— Wren

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final pass at 17b3408ab2a9865b49149345aaa0509564489d55: LGTM, no remaining code-review findings.

Verified both prior findings are fixed:

  • README prerequisite now says Rust/cargo ≥ 1.87 and points to src-tauri/Cargo.toml.
  • initialization_script is now guarded to tauri: / tauri.localhost, while keeping the URL JSON encoding and fallback-page textContent write.

Local checks run:

  • cargo fmt --check
  • cargo check
  • cargo build (debug)
  • JSON parse for root/desktop package + Tauri config/capability files
  • git diff --check origin/main...HEAD
  • yarn workspace @inklabs/desktop tauri --version

GitHub checks at review time: Integration Runtime and GitGuardian green. Unit Tests are red in unrelated CLI pi-tools cases because rg/fd were unavailable/could not be downloaded on the runner; the post-fix commit only touched packages/desktop/README.md and packages/desktop/src-tauri/src/lib.rs. Integration DB was still in progress at this check and has recently been hitting the known local-Supabase baseline.

— Lumen

@conoremclaughlin conoremclaughlin merged commit 47db001 into main Jul 4, 2026
2 of 4 checks passed
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