diff --git a/.github/workflows/ci-client-web-svelte.yml b/.github/workflows/ci-client-web-svelte.yml new file mode 100644 index 0000000..643520d --- /dev/null +++ b/.github/workflows/ci-client-web-svelte.yml @@ -0,0 +1,75 @@ +--- +name: CI-Client-Web-Svelte +permissions: {} + +on: + pull_request: + paths: + - 'crates/client-web-svelte/**' + - '.github/workflows/ci-client-web-svelte.yml' + push: + branches: + - master + paths: + - 'crates/client-web-svelte/**' + - '.github/workflows/ci-client-web-svelte.yml' + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + svelte: + name: Svelte Client (${{ matrix.run }}) + permissions: + contents: read + strategy: + fail-fast: false + matrix: + run: + - check + - lint + - test + - build + runs-on: ubuntu-latest + defaults: + run: + working-directory: crates/client-web-svelte + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + cache: npm + cache-dependency-path: crates/client-web-svelte/package-lock.json + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Check (svelte-check) + if: matrix.run == 'check' + run: npm run check + + - name: Lint (oxlint) + if: matrix.run == 'lint' + run: npm run lint:ci + + - name: Test (vitest) + if: matrix.run == 'test' + run: npm run test + + - name: Build + if: matrix.run == 'build' + run: npm run build + + - name: Upload build artifact + if: matrix.run == 'build' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + if-no-files-found: error + name: client-web-svelte-dist + path: crates/client-web-svelte/dist diff --git a/PROPOSAL.md b/PROPOSAL.md new file mode 100644 index 0000000..232031f --- /dev/null +++ b/PROPOSAL.md @@ -0,0 +1,222 @@ +# Proposal: Rewrite the Koko web client in Svelte 5 (+ optional Tauri desktop shell) + +**Status:** Proof-of-concept / proposal — not a merged plan. Prepared for discussion. +**Scope:** `crates/client-web` (the web UI). No backend changes are required or proposed. +**Worktree:** `poc/svelte-rewrite` (companion PoC in `crates/client-web-svelte/`). + +--- + +## TL;DR + +1. **Rewrite `client-web` from vanilla TypeScript into Svelte 5**, shipped as a static SPA (SvelteKit with `adapter-static`, SSR disabled). The rewrite is well-supported, modest effort, and the single biggest win is **deleting the hand-rolled `domPatcher.ts`** (~290 lines) and most of `eventBindings.ts` (~1,395 lines) — Svelte's reactivity replaces them natively. +2. **A Tauri desktop shell is viable and lower-risk than typical** because Koko's server already builds on `tao` + `tray-icon` + `keyring` — the exact crates Tauri is made of. Tauri is an *optional follow-on*, not a prerequisite. The recommended architecture keeps the HTTP-served SPA for remote clients (phones, tablets, LAN TVs) **and** adds a Tauri shell that loads the same frontend for desktop. +3. **The real risk is `playbackController.ts`** (video/audio/YouTube trailers), not the framework swap. That file needs careful, incremental porting. The logs-view PoC in this worktree de-risks everything *except* playback. +4. **Recommendation:** pursue the Svelte rewrite incrementally (view-by-view, keeping the vanilla client buildable in parallel), and treat the Tauri shell as a separate, later decision once the rewrite has shipped. + +--- + +## 1. Why consider this at all + +The current client is a **vanilla TypeScript SPA** (~11,750 lines across `src/app/`) bundled with Vite. It has exactly one runtime dependency (`lucide`). It is genuinely lean, and that's worth preserving. + +However, it implements its own UI framework by hand: + +- **`domPatcher.ts`** — a custom DOM reconciler with keyed reordering, focus/scroll/selection snapshot+restore, form-control syncing, and a special-case guard so `