feat(web): convert the UI to a React + Vite + TypeScript SPA #2
Loading…
Reference in a new issue
No description provided.
Delete branch "malcolmmielle/react-migration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Convert the Federated Reviews web interface from Jinja2 + vanilla JS to a React + Vite + TypeScript SPA in a new self-contained
react-app/folder, per the handoff specREACT-MIGRATION.md(committed at the repo root). The backend stays FastAPI with its architecture unchanged; FastAPI now serves the built SPA at/with a catch-all registered after all API / ActivityPub / auth //staticroutes so it never shadows them. Desktop keeps the original look (380 px sidebar, 400 px panels, identical design tokens); phones get a real mobile-first layout (draggable bottom sheet, floating top search, full-screen slide-up detail/form,100dvh, ≥ 44 px touch targets) replacing the old single@mediahack. The bundle targetses2020so the same build can later run in a SailfishSailfish.WebViewshell.Merged with current
main(which had moved ahead with CSRF middleware, Nominatim geocode proxies, a pytest suite, corrected follows semantics, and a package reorganization): the SPA now uses main's/api/geocode+/api/reverse-geocodeproxies instead of calling Nominatim directly, posts the session CSRF token with its form-encoded logout (JSON requests are CSRF-exempt), andGET /api/mereturns the token alongside the user. My earlier fan-out fix was dropped — main already resolves follower inbox URLs (get_follower_inboxes). One pre-existing bug fix remains: HTTP Signatures now cover theHost: host:portheader exactly as the client sends it, which makes the documented two-instance federation test (ports 8000/8001) actually pass; standard-port behaviour is unchanged.Changes
react-app/(new; ownpackage.json, rootpackage.jsonstays{}): Vite + React + TypeScript + react-leaflet scaffold;build.target: "es2020"(Sailfish Gecko requirement); devserver.proxyon 5173 forwarding/api,/actor,/.well-known,/login,/register,/logout,/review,/staticto:8000;.gitignoreextended fornode_modules/+dist/.app/static/app.js(503 lines) into components:App(fetches/api/me),MapPage(layout/state/data flow),MapView(react-leaflet: OSM tiles, rating-coloured teardropdivIconmarkers, popups,moveendbounds-filtered reload, click-to-place, draggable placement marker, focus padding),ReviewList,ReviewDetail,ReviewForm(place search, address auto-fill, star rating),SearchBox, reusable debouncedNominatimSearch(350 ms),StarRating, plususeMediaQuery/useBottomSheethooks.style.cssported with identical design tokens (IDs → classes; panel show/hide as mount keyframes). React JSX replaces the oldinnerHTML/escHtmlrendering, so popups and lists are XSS-safe by construction.100dvhheights with100vhfallback, ≥ 44 px touch targets. Map focus padding adapts per viewport.GET /api/me→{id, username, display_name, csrf_token}or 401 JSON; the_LoginRequiredhandler returns401 {"detail": ...}when the request'sAcceptincludesapplication/json(orX-Requested-Withis set), keeping the 303 →/loginredirect for plain browser form posts;POST /api/reviewsaccepts JSON bodies in addition to form-encoded, with 422 validation;main.pymountsreact-app/dist/assetsand registers the SPA catch-all last; the oldGET /Jinja route removed;app/static/app.jsandapp/templates/index.htmldeleted;base.htmlno longer loads Leaflet from unpkg;style.csskept (still used by the remaining Jinja pages) with a pointer comment.app/activitypub/signing.py—sign_requestsigned theHostheader without the port, so signature verification failed against any server on a non-standard port (exactly the README's two-instance setup). Now signs the host exactly as the HTTP client sends it (default ports still omitted). Locked in with a new test.get_csrf_token(client, "/")call sites moved to/register(the index page no longer renders a form),test_index_pageis now SPA-aware (200 + React root whenreact-app/distis built, 404 with thenpm run buildhint otherwise), plus one new signing test.react-app/, hot-reload dev section, project layout, tech stack, API reference (incl./api/me), and the review-creation smoke test now logs in with a CSRF token + cookie jar and shows form + JSON variants.sailfish-app/Qt/QML shell, Capacitor wrapper, JWT/token auth + JSON auth endpoints, PWA, pagination UI.Testing
uv run pytest→ 68 passed (67 from main + 1 new signing test)uv run ruff check app/ tests/→ all checks passed;ruff format --checkclean on all touched files (4 unformatted files pre-exist onmain, left untouched to keep the diff focused)uv run basedpyright app/→ 0 errorsnpm install && npm run build(tsc strict + Vite, es2020) → green;npm run devproxy verified via curl (API/auth/ActivityPub/static proxied, session cookie works through the proxy)/+ deep links;/api/me401 unauth / 200 withcsrf_token; login/logout with CSRF → 303; form-encoded review POST with csrf → 200; JSON POST (CSRF-exempt) → 200; missing-csrf → 403; unauth JSON POST → 401 JSON; unauth form POST → 303 →/login; geocode proxies → 200; WebFinger / actor (Accept: application/activity+json) / outbox / followers → 200;/review/{id}SSR → 200;/static/app.js→ 404/api/reviews(fan-out ok:1 / fail:0)popup.update()when aPopup's children identity changes while open, and Leaflet auto-pans the map to the popup — fixed by memoizing marker positions and popup children.Screenshots
Same scenarios, same viewports — before (
main, Jinja + vanilla JS) vs after (this branch, React SPA). Images are pinned to a review-only assets ref (assets/react-migration) so they survive branch updates:main)