Todo 19 — news-web UI (CSR) with Leptos 0.8 Completed: 2026-09-01 Scope - Scaffolded `crates/news-web` as a Leptos 0.8 client-side-rendered (CSR) web app, mirroring `runway-web`'s Trunk + Tailwind v4 toolchain. - Implemented three API client routes against the same-origin `/api/*` proxy: - GET /api/stories - POST /api/feedback - GET /api/config - Built three UI views: - Story list: table of clusters with title, source count, importance, relevance, percentile badges, server decision, and per-row feedback buttons (Interested / Not interested). - Config view: read-only summary of topics, notification settings, and sources. - Error / loading states for both data fetches and feedback actions. - Kept the WASM bundle lean by avoiding `news-core` and any markdown/chart libraries. Verification - `cargo check -p news-web --target wasm32-unknown-unknown` passes. - `trunk build --release` succeeds; generated WASM is `news-web-263e7ab3a1a1c8aa_bg.wasm` at 525 KiB (well under the 1.8 MiB budget enforced by todo 20). - `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, and `cargo test --workspace` all pass (87 tests total). Playwright QA — happy path - Seeded a temporary SQLite database with one source and one story cluster ("Mars Rover Finds Water"). - Started `target/debug/news-server` on port 3000 and `trunk serve` on port 8080 with Trunk proxying `/api` to the backend. - Navigated to http://127.0.0.1:8080/ in Playwright. - Asserted the story title "Mars Rover Finds Water" rendered in the table. - Clicked the "Interested" feedback button. - Asserted the per-row "Saved" success indicator appeared. - Verified the network request log contained: POST http://127.0.0.1:8080/api/feedback => 200 OK - Captured screenshot: `.omo/evidence/task-19-news-triage.png` Playwright QA — failure path - Stopped the backend server (port 3000). - Reloaded http://127.0.0.1:8080/ in Playwright. - Asserted the visible error state text "Could not load stories" appeared. - Verified the network request log contained: GET http://127.0.0.1:8080/api/stories => 500 Internal Server Error - Captured screenshot: `.omo/evidence/task-19-news-triage-failure.png` Evidence files - `.omo/evidence/task-19-news-triage.png` — happy-path story list with feedback success state. - `.omo/evidence/task-19-news-triage-failure.png` — failure-path error state. - `.omo/evidence/task-19-news-triage.txt` — this file. Notes / gotchas - Leptos 0.8's `Action::new` requires `Send` futures, which are not available on `wasm32-unknown-unknown`. On WASM, use `Action::new_local` for actions backed by `gloo_net` (or any non-Send JS future). - `news-server` stores `source.kind` and `raw_items.kind` as snake_case strings (`rss_atom`, `news`). Seeding with `rss` or any other string causes a startup parse error (`InvalidStoredValue`). - The Playwright MCP was configured to launch Google Chrome at `/opt/google/chrome/chrome`; on this Arch box that required installing the system `chromium` package and symlinking `/opt/google/chrome/chrome -> /usr/bin/chromium` so the MCP could launch the browser.