Skip to content

docs: tauri port exploration plan#73

Draft
andykais-claude wants to merge 2 commits into
mainfrom
cursor/tauri-port-plan-1e82
Draft

docs: tauri port exploration plan#73
andykais-claude wants to merge 2 commits into
mainfrom
cursor/tauri-port-plan-1e82

Conversation

@andykais-claude

@andykais-claude andykais-claude commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Adds docs/design/tauri-port.md, an experimental design proposal for porting @forager/web to a native Tauri v2 desktop app while keeping a client/server architecture.

Updates from review (2nd commit)

  • Auth dropped everywhere. Per review, access control is being designed separately and is explicitly out of scope. Removed auth_token config, signed-URL discussion, the "Auth model" risk, and all the connection.sign(...) plumbing — BaseController now just template-strings against connection.base_url.
  • Always serve the HTML. Dropped the serve_frontend toggle and the forager serve --no-frontend flag. The static SPA is always mounted at /; if the user doesn't want a browser hitting the server, they simply don't open it in one. The doc explains the (lack of) downside in §4.
  • web.desktop.profiles collapsed into a flat web.desktop.base_url. Multi-server profile management is more than this experimental demo needs. Removed the --profile NAME CLI flag too.
  • In-app connection picker removed. The settings GUI is a separate effort.
  • Linux-only first target. Removed the macOS-WebKit-quirks risk; downsized the CI matrix to a single Linux tauri build; release pipeline ships forager-gui-linux.
  • SvelteKit experimental.* risk dropped per review.
  • Tauri custom URI scheme kept in the doc, but explicitly relabelled as "future consideration" rather than a planned phase-7 work item — that's how it reads in §6 now.

What the plan still proposes

  • Architecture split of today's @forager/web into:
    • @forager/server — headless Deno HTTP server (RPC + /files/*), no SvelteKit.
    • @forager/frontend — pure Svelte 5 SPA built with adapter-static, with a Connection abstraction (just { base_url } now) the controller uses for URL building.
    • @forager/desktop — Tauri shell that bundles the static frontend and exposes the base URL via window.__FORAGER_CONNECTION__.
    • @forager/web kept as a thin façade for backwards compat with the JSR consumer story.
  • Media + thumbnail streaming: always proxy through HTTP. Tauri custom URI scheme called out as a future consideration only.
  • Config schema additions (web.server.* and web.desktop.base_url), with a Zod .transform() to preserve every existing forager.yml.
  • CLI shape: forager serve, forager gui, and forager gui --serve-local. Tauri ships as a sidecar (forager-gui-linux) because deno compile can't embed it.
  • Phased rollout (7 phases, each ending in a buildable + testable state; the browser experience never breaks).
  • Risks / open questions (now 4): Deno+Tauri dev ergonomics, RPC CORS preflight, @forager/web JSR consumers, CI binary distribution.
  • File-by-file table of what's new, modified, or removed.

No implementation in this PR — this is plan-only.

Testing

No code changes — documentation only. Verified the doc renders with reasonable structure and no broken local references.

Open in Web Open in Cursor 

Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
Comment thread docs/design/tauri-port.md Outdated
New responsibilities the standalone server needs:

1. **CORS** — Tauri's webview uses a custom scheme (`tauri://localhost` or `http://tauri.localhost`); the server must allow it. Same for arbitrary LAN origins if the user opens `http://my-nas:8000` in a browser. Configurable allow-list via `forager.yaml`.
2. **Optional bearer token auth** — opt-in via config (`web.server.auth_token`). When set, RPC calls and `/files/*` requests must include `Authorization: Bearer <token>`. For media tags in `<img>`/`<video>`, we either:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

drop any auth considerations. These are interesting, but ultimately out of scope. I have future plans to handle this differently

Comment thread docs/design/tauri-port.md Outdated
When the Tauri app talks to a _remote_ Forager, the file lives on the remote machine, but the `<video>` element is local. There are exactly two reasonable choices:

1. **Always proxy through HTTP** (the only one that actually works in the remote case). Both browser and Tauri webview point `<video src>` and `<img src>` at the remote `@forager/server`'s `/files/...` endpoints. Range requests already work. This is what the architecture above assumes.
2. **Tauri custom URI scheme for local-only.** Optional optimization for `--serve-local`, where the Tauri app could register a `forager-media://` scheme handler in Rust that reads files directly off disk and skips HTTP entirely. Not part of the first cut; called out so we don't paint ourselves into a corner.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

this is cool. I agree we can punt for now. Keep this in the design doc as a future consideration.

Comment thread docs/design/tauri-port.md Outdated
2. Resolve `web.desktop.server` (see §8 schema). If it's `local`, fail — the Tauri shell alone has no backend (and the user should use `forager gui --serve-local` for that flow; see §9).
3. Otherwise expose `{ base_url, token }` to the frontend.

A second-pass enhancement (probably worth doing in the first iteration anyway): a tiny in-app "connection picker" screen for switching between known remotes without editing yaml.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

ignore this for now. Settings in general need a GUI that I will solve separately.

Comment thread docs/design/tauri-port.md Outdated
## 11. Risks / open questions

1. **Deno + Tauri dev ergonomics.** Tauri assumes Node-ish tooling. Vite + Deno works (it's already in use), but the `beforeDevCommand` invocation needs `deno` on PATH in dev environments. Worth verifying early in Phase 5 that `tauri dev` and `tauri build` cleanly drive a Deno-based Vite build, especially on Windows where shell quoting differs.
2. **SvelteKit `experimental.async` / `experimental.remoteFunctions`.** Both are enabled in `svelte.config.js`. We should check whether `adapter-static` is compatible with the routes that currently rely on these flags. If they're only used to allow top-level `await` in components, dropping the adapter is fine. If we depend on remote functions, those become regular `fetch` calls into `@forager/server` instead.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

you can ignore these and drop them. We don't need this.

Comment thread docs/design/tauri-port.md Outdated

1. **Deno + Tauri dev ergonomics.** Tauri assumes Node-ish tooling. Vite + Deno works (it's already in use), but the `beforeDevCommand` invocation needs `deno` on PATH in dev environments. Worth verifying early in Phase 5 that `tauri dev` and `tauri build` cleanly drive a Deno-based Vite build, especially on Windows where shell quoting differs.
2. **SvelteKit `experimental.async` / `experimental.remoteFunctions`.** Both are enabled in `svelte.config.js`. We should check whether `adapter-static` is compatible with the routes that currently rely on these flags. If they're only used to allow top-level `await` in components, dropping the adapter is fine. If we depend on remote functions, those become regular `fetch` calls into `@forager/server` instead.
3. **Auth model.** The signed-URL approach for `/files/*` needs a thought-out token format (probably HMAC over `<path>?expires=…&token=…` with the server's `auth_token` as key). If we punt and only support unauthenticated `127.0.0.1` for now, that's fine — but document it.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

again, ignore auth for now

Comment thread docs/design/tauri-port.md Outdated
2. **SvelteKit `experimental.async` / `experimental.remoteFunctions`.** Both are enabled in `svelte.config.js`. We should check whether `adapter-static` is compatible with the routes that currently rely on these flags. If they're only used to allow top-level `await` in components, dropping the adapter is fine. If we depend on remote functions, those become regular `fetch` calls into `@forager/server` instead.
3. **Auth model.** The signed-URL approach for `/files/*` needs a thought-out token format (probably HMAC over `<path>?expires=…&token=…` with the server's `auth_token` as key). If we punt and only support unauthenticated `127.0.0.1` for now, that's fine — but document it.
4. **CORS preflight for RPC.** `@andykais/ts-rpc` does `PUT` requests with JSON bodies, which is preflighted by browsers. The server needs to handle `OPTIONS` correctly for any cross-origin client. Not hard, but easy to forget.
5. **Tauri webview quirks per OS.** WebKit on macOS (in particular) has historically been finicky about `<video>` autoplay, Range responses, and `MediaSource`. Phase 5 testing should explicitly include media playback, since that's our most fragile UX.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

lets ignore mac for now. The initial goal is getting this working on linux

Comment thread docs/design/tauri-port.md Outdated
4. **CORS preflight for RPC.** `@andykais/ts-rpc` does `PUT` requests with JSON bodies, which is preflighted by browsers. The server needs to handle `OPTIONS` correctly for any cross-origin client. Not hard, but easy to forget.
5. **Tauri webview quirks per OS.** WebKit on macOS (in particular) has historically been finicky about `<video>` autoplay, Range responses, and `MediaSource`. Phase 5 testing should explicitly include media playback, since that's our most fragile UX.
6. **`@forager/web` JSR consumers.** The package is published to JSR and consumed by `@forager/cli`. If we delete it, downstream tooling breaks. Easier path: keep `@forager/web` as a façade re-exporting `Server` from `@forager/server`. Plan accordingly in Phase 3.
7. **CI binary distribution.** The current release flow only ships the Deno-compiled CLI binaries. Adding Tauri increases artifact size and OS-specific complexity (signing on macOS, MSIX/WiX on Windows). For an experimental project we can ship unsigned binaries to start.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

this is acceptable

Comment thread docs/design/tauri-port.md Outdated
config, // the resolved PackagesConfig
port: 8000,
hostname: '0.0.0.0', // explicit, default '127.0.0.1'
serve_frontend: true, // mount static SPA at '/'

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

can we simplify these by always serving the html? Whats the downside to doing this?

Comment thread docs/design/tauri-port.md Outdated
Comment on lines +251 to +254
desktop:
# connection profile to use on launch; can be a named profile or 'local'
server: default
profiles:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

this abstraction is neat, but out of scope for an experimental demo. Just make this a flat base url config:

Suggested change
desktop:
# connection profile to use on launch; can be a named profile or 'local'
server: default
profiles:
desktop:
base_url: http://127.0.0.1:8000

- Drop auth/bearer-token considerations (out of scope; designed separately)
- Drop @forager/web JSR risk... no wait, keep that
- Drop macOS-specific WebKit risk; first target is Linux only
- Drop SvelteKit experimental-flags risk
- Replace web.desktop.profiles map with a flat web.desktop.base_url
- Always serve frontend HTML (no serve_frontend toggle, no --no-frontend flag)
- Drop in-app connection picker idea (general settings GUI is a separate effort)
- Keep Tauri custom URI scheme as an explicit future consideration
- Renumber risks; downsize CI build matrix to Linux only

Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
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.

3 participants