Skip to content

impl(web,cli): static SPA build + thin web.Server (tauri port phase 3)#77

Draft
andykais-claude wants to merge 1 commit into
cursor/tauri-frontend-phase2-1e82from
cursor/tauri-static-spa-phase3-1e82
Draft

impl(web,cli): static SPA build + thin web.Server (tauri port phase 3)#77
andykais-claude wants to merge 1 commit into
cursor/tauri-frontend-phase2-1e82from
cursor/tauri-static-spa-phase3-1e82

Conversation

@andykais-claude

Copy link
Copy Markdown
Collaborator

Phase 3 of the Tauri port plan. Stacked on phase 2; base branch is cursor/tauri-frontend-phase2-1e82. Please review/merge phases 1 and 2 first, then this.

The SvelteKit app is now a pure static SPA — there is no SSR, no +server.ts routes, no hooks.server.ts, no SvelteKit runtime in the production binary. @forager/web becomes a thin static-asset host that mounts ForagerServer for /rpc/* and /files/* and serves index.html as the SPA fallback for everything else.

Walkthrough

forager_phase3_static_spa_smoketest.mp4

End-to-end browser test of the static SPA against the compiled CLI binary: / redirects client-side to /browse, direct navigation to /tags and /tags/cat (URLs that don't exist on disk) correctly serves the SPA shell + client-side routing, video plays via Range requests.

Browse view rendered by the static SPA
Direct navigation to /tags works via SPA fallback
Deep client route /tags/cat works via SPA fallback
Video playback via Range requests still works

What changed

Frontend (packages/web/src)

  • Deleted hooks.server.ts, +layout.server.ts, +page.server.ts, and all three +server.ts files (/rpc/[signature], /files/media_file/[id], /files/thumbnail/[id]). Phase 1 had already moved their logic into @forager/server — they were thin shims at that point.
  • New +layout.ts fetches config via RPC at boot and propagates it as data.config so the controllers don't need to change.
  • New +page.ts + +page.svelte perform a client-side goto('/browse', { replaceState: true }), replacing the old 307 server redirect.

Custom adapter (packages/web/adapter/adapter.js)

  • Stripped to a static-only build. Equivalent to @sveltejs/adapter-static with fallback: 'index.html', plus the bytes-import packaging that lets deno compile embed the SPA inside the CLI binary.
  • Dropped builder.writeServer(...) and the strip_type_imports hack on the SvelteKit server bundle (no server bundle exists anymore).

Static frontend host (packages/web/adapter/lib/mod.ts)

  • The embedded SvelteKit kitServer + manifest are gone. Server.handle_request is now: ForagerServer.try_handle_request(req) → static file via serveDir → SPA fallback (build/static/index.html).
  • _app/immutable/* still gets cache-control: public, max-age=31536000, immutable. The SPA shell gets cache-control: no-cache so deploys roll out instantly.
  • The kit option block is gone from ServerOptions; CLI no longer plumbs FORAGER_INSTANCE/FORAGER_CONFIG via env.
  • preview mode (deno task --cwd packages/web/adapter/lib preview) still works; entrypoint shape is unchanged.
  • packages/web/adapter/lib/shim_config.ts re-exports @forager/server/config so the existing @forager/web/config import path keeps working for downstream consumers.

vite dev (packages/web/vite.config.ts)

  • Added a proxy: /rpc and /files forward to FORAGER_API_URL (default http://127.0.0.1:8000). deno task --cwd packages/web dev now expects a forager gui/forager serve running separately. Phase 4 will add CORS so the dev server can also talk cross-origin.

CLI (packages/cli/src/cli.ts)

  • init and gui simplified — they construct web.Server with just { forager, config, ... } (no kit.env).

Numbers

Metric Phase 1 Phase 3 Delta
packages/web/adapter/lib embedded in CLI binary 1.04 MB 227 KB −78%
SvelteKit server bundle in build/server/* ~837 KB gone
Files in CLI binary tree under web/ 200+ 26

The entire SvelteKit server runtime (Server, manifest, chunks/internal.js, chunks/migration_v11.js, etc.) is no longer shipped.

Testing

  • deno task --cwd packages/web build:local — clean static SPA build (26 files: _app/*, favicon.png, index.html).
  • deno check packages/web/adapter/lib/mod.ts — clean.
  • deno check packages/cli/src/cli.ts — clean.
  • deno task compile:local — full CLI binary builds; web tree is 227 KB.
  • deno task --cwd packages/cli test — 2/2 pass.
  • deno test packages/web/test/connection.test.ts — 8/8 pass (phase 2 unit tests).
  • ✅ Curl smoke test against the compiled binary:
    • GET / → 200 SPA shell (1217 bytes); the SPA then goto('/browse').
    • GET /browse, GET /tags, GET /tags/anything → all 200 + same SPA shell (fallback works for arbitrary client routes).
    • GET /favicon.png → 200 image/png 4461 bytes.
    • GET /_app/version.json → 200 (real static file).
    • PUT /rpc/forager.media.search → JSON RPC payload.
    • GET /files/thumbnail/1 → 200 image/jpeg 36544 bytes.
    • GET /files/media_file/2 with Range: bytes=0-1023 → 206 + content-range: bytes 0-1023/400240.
  • ✅ Browser smoke test (recorded above): root redirect, direct navigation to /tags and /tags/cat, video playback all work end-to-end.

Follow-ups (not in this PR)

  • Phase 4 — web.server.cors_allow_origins, forager serve command, config schema additions.
  • Phase 5 — @forager/desktop Tauri shell scaffolding.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Phase 3 of the Tauri port plan. The SvelteKit app is now a pure static
SPA; the SSR/server runtime is gone. @forager/web becomes a thin static
asset host that mounts ForagerServer for /rpc and /files.

Frontend (packages/web/src)
  - hooks.server.ts: deleted. No more SSR-time Forager construction.
  - +layout.server.ts: deleted. Replaced by +layout.ts that fetches the
    config via RPC at boot. Layout still propagates data.config to child
    pages so the controllers don't change.
  - +page.server.ts: deleted. Replaced by +page.ts + +page.svelte that
    perform a client-side goto('/browse', { replaceState: true }).
  - routes/rpc/[signature]/+server.ts, routes/files/media_file/[id]/+server.ts,
    routes/files/thumbnail/[id]/+server.ts: deleted. The handlers all live
    in @forager/server now and are mounted by web.Server directly.

Custom adapter (packages/web/adapter/adapter.js)
  - Stripped to a static-only build. Equivalent to @sveltejs/adapter-static
    with fallback: 'index.html', plus the bytes-import packaging so
    'deno compile' can embed the SPA inside the CLI binary.
  - Drops writeServer + the strip_type_imports hack.

@forager/web runtime (packages/web/adapter/lib/mod.ts)
  - Embedded SvelteKit kitServer + manifest gone.
  - Server.handle_request: ForagerServer.try_handle_request(req) → static
    file via serveDir → SPA fallback (build/static/index.html). Immutable
    asset routes still get long cache-control; SPA shell gets no-cache.
  - The 'kit' option block is gone from ServerOptions; CLI no longer
    plumbs FORAGER_INSTANCE/FORAGER_CONFIG via env.
  - 'preview' mode still works; entrypoint shape is unchanged.
  - shim_config.ts re-exports @forager/server/config so the existing
    @forager/web/config import path keeps working for downstream consumers.

vite dev (packages/web/vite.config.ts)
  - Added a proxy: /rpc and /files forward to FORAGER_API_URL (default
    http://127.0.0.1:8000). 'deno task --cwd packages/web dev' now expects
    a 'forager gui'/'forager serve' running separately.

CLI (packages/cli/src/cli.ts)
  - 'init' and 'gui' no longer pass kit.env. They construct web.Server
    with just forager + config.

Result: web/adapter/lib drops from 1.04 MB to 227 KB embedded in the CLI
binary (the entire SvelteKit server bundle is gone). Browser experience,
RPC payloads, /files/* responses (including Range), and direct navigation
to /browse, /tags, /tags/<slug> all verified end-to-end against the
compiled binary.

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.

2 participants