docs: tauri port exploration plan#73
Conversation
Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
| 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: |
There was a problem hiding this comment.
drop any auth considerations. These are interesting, but ultimately out of scope. I have future plans to handle this differently
| 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. |
There was a problem hiding this comment.
this is cool. I agree we can punt for now. Keep this in the design doc as a future consideration.
| 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. |
There was a problem hiding this comment.
ignore this for now. Settings in general need a GUI that I will solve separately.
| ## 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. |
There was a problem hiding this comment.
you can ignore these and drop them. We don't need this.
|
|
||
| 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. |
| 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. |
There was a problem hiding this comment.
lets ignore mac for now. The initial goal is getting this working on linux
| 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. |
| config, // the resolved PackagesConfig | ||
| port: 8000, | ||
| hostname: '0.0.0.0', // explicit, default '127.0.0.1' | ||
| serve_frontend: true, // mount static SPA at '/' |
There was a problem hiding this comment.
can we simplify these by always serving the html? Whats the downside to doing this?
| desktop: | ||
| # connection profile to use on launch; can be a named profile or 'local' | ||
| server: default | ||
| profiles: |
There was a problem hiding this comment.
this abstraction is neat, but out of scope for an experimental demo. Just make this a flat base url config:
| 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>
5d4fc0a to
eae8011
Compare
Adds
docs/design/tauri-port.md, an experimental design proposal for porting@forager/webto a native Tauri v2 desktop app while keeping a client/server architecture.Updates from review (2nd commit)
auth_tokenconfig, signed-URL discussion, the "Auth model" risk, and all theconnection.sign(...)plumbing —BaseControllernow just template-strings againstconnection.base_url.serve_frontendtoggle and theforager serve --no-frontendflag. 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.profilescollapsed into a flatweb.desktop.base_url. Multi-server profile management is more than this experimental demo needs. Removed the--profile NAMECLI flag too.tauri build; release pipeline shipsforager-gui-linux.experimental.*risk dropped per review.What the plan still proposes
@forager/webinto:@forager/server— headless Deno HTTP server (RPC +/files/*), no SvelteKit.@forager/frontend— pure Svelte 5 SPA built withadapter-static, with aConnectionabstraction (just{ base_url }now) the controller uses for URL building.@forager/desktop— Tauri shell that bundles the static frontend and exposes the base URL viawindow.__FORAGER_CONNECTION__.@forager/webkept as a thin façade for backwards compat with the JSR consumer story.web.server.*andweb.desktop.base_url), with a Zod.transform()to preserve every existingforager.yml.forager serve,forager gui, andforager gui --serve-local. Tauri ships as a sidecar (forager-gui-linux) becausedeno compilecan't embed it.@forager/webJSR consumers, CI binary distribution.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.