Commit Graph

14 Commits

Author SHA1 Message Date
renovate[bot] 6bc3d0ab42 fix(deps): update dependency react-i18next to v17 2026-07-24 19:50:33 +00:00
renovate[bot] d5adc5c392 fix(deps): update dependency i18next to v26 2026-07-24 19:39:32 +00:00
renovate[bot] 7c2cd4f538 fix(deps): update dependency react-router to v8 2026-07-23 15:44:06 +00:00
Louis King c8cc049651 feat(web): React SPA shell, markdown migration, vitest coverage, v0.17 docs
- Migrate footer, error page, and announcements to React; slim Jinja2
  shell to SEO/head/config/fonts/theme-init only
- Replace server-side markdown rendering with react-markdown (removes
  Python `markdown` dependency); custom pages + announcements ship
  raw markdown to the client
- Add heading-anchor deep-links with CSS override for inherited colors
- Extract pure helpers from pages (messageHelpers, mapMath, routesHelpers,
  profileHelpers, packetHelpers, packetGroupHelpers) for unit testability
- Add comprehensive vitest suite: 59 test files, 315 tests covering all
  components, pages, and extracted helpers; global i18next mock + matchMedia
  stub + AbortController-aware apiMock in test infrastructure
- Fix SortableTable test DOM nesting (<th> inside proper <table> context)
- Update docs for v0.17.0: upgrading.md release notes, README.md project
  structure + build description, i18n.md path fix
- Delete REACT_MIGRATION.md (migration complete, unreferenced)
- Add announcements dismiss-persistence + custom-page 404 E2E specs
2026-07-22 22:55:20 +01:00
Louis King 94dd4c9b42 test(e2e): replace Python e2e suite with Playwright (headless Chromium)
Replaces the httpx-based smoke tests in tests/e2e/ with a browser E2E suite
under e2e/, running against a self-contained throwaway stack that never
touches the local development database.

Stack & data isolation (e2e/docker-compose.test.yml):
- Own ephemeral Postgres 17 (schema via the `migrate` service / Alembic),
  distinct project name + named volumes, no host DB port, no \${VAR}
  interpolation. `make e2e-down` destroys everything.
- OIDC enabled with a known session secret; WEB_AUTO_REFRESH_SECONDS=2 so
  polling is assertable; CONTENT_HOME mounts a test markdown page.
- Deterministic seeder (e2e/seed_data.py) run via global setup inside the
  collector container: nodes/observers with `area` tags, adverts, messages
  on the public (17) + a custom channel, raw packets + path hops keyed to
  node prefixes, a route + health/history, profiles + adoptions, and
  event_observers rows so the observer filter/badges resolve.

Auth & tests:
- Forged signed `meshcore-session` cookies (e2e/mint_session.py,
  itsdangerous) for admin/member identities -> storageState; no real IdP
  needed. 31 specs across 12 files cover global nav/theme, profile
  menu+edit, home hero, dashboard widgets, list filters/auto-refresh/row
  actions, observer toggles, the path-node overlay, map filters +
  show-labels, members, markdown pages, and routes add/edit/delete
  (persistence, validation, confirm dialog). workers:1 / fullyParallel:false
  against the single shared backend; targeted data-testids added to React
  components for stable selectors.

Fixes surfaced while running the suite on fresh Postgres:
- Migration 5e3b712ccf10 aborted on Postgres: its route-health backfill
  queries the live Route model (which now has max_path_length) before that
  column exists. The swallowed error left the transaction aborted, killing
  the subsequent alembic_version stamp. Wrapped the backfill in a SAVEPOINT
  so a failure rolls back cleanly without blocking the migration.
- Restored the full ABUSE_* env set required by the MQTT broker, and set
  the web service's API_KEY to the admin key (admin writes go through the
  proxy as a Bearer token).

31/31 passing; tsc (frontend+e2e), pytest (1460), and pre-commit green.
2026-07-22 10:41:53 +01:00
Louis King de9784211d feat(web): adopt TanStack Query for SPA data layer; consolidate shared UI
Migrate the React SPA off the bespoke useApiFetch hook and raw useEffect fetches to TanStack Query: useQuery/useQueries for reads, useMutation + invalidateQueries for writes; a central query-key factory and invalidation helpers mirroring the backend cache_invalidation prefixes (channels/routes/nodes/messages/profiles/dashboard/adoptions); polling via refetchInterval (useAutoRefresh now returns pause state only); RouteCard self-fetches its detail/history, dropping the hand-rolled caches. Adds QueryClientProvider in App, a renderWithProviders test helper, and deletes useApiFetch.

Also consolidates recurring UI into reusable components (Breadcrumbs, ListToolbar, Modal/ConfirmDialog, NotFoundState, TimeAgo, Definition, CopyableValue, MeshQrCode, Badges, SectionGroup, PageHeader) and fixes the FilterForm clear button, merged toggle wrappers, and role-aware channels/messages cache keys so client invalidation matches the server.

Verified: tsc --noEmit clean, vitest 113 passed, pytest test_web 251 + test_cache 119 passed, pre-commit --all-files green.
2026-07-21 23:24:58 +01:00
Louis King 527c860bf8 feat(web): frontend CI, vitest suite, navbar→React SPA shell — Phase 5
Frontend CI (closes the no-coverage gap for the TSX):
- ci.yml: new 'frontend' job — npm ci, tsc --noEmit, test:frontend, build.
- package.json: engines.node>=20, test:frontend/typecheck scripts.

vitest unit + component tests (jsdom, @testing-library/react):
- utils/charts.test.ts — tier math + every chart builder.
- utils/format.test.ts — parseAppDate, formatNumber, truncateKey, emoji helpers,
  formatRelativeTime.
- components/Navbar.test.tsx — feature-gated nav, custom pages, OIDC/maintenance
  auth gating.
- components/Announcements.test.tsx — banner rendering, ordering, dismiss +
  sessionStorage (covers behaviour moved out of the Python suite).

Navbar → React (full SPA shell):
- New Navbar/ThemeToggle/Announcements components + useNavItems hook (shared
  feature-gated nav for desktop + mobile); nav uses react-router NavLink
  (client-side nav + auto active) — drops the imperative data-nav-link bridge.
- main.tsx single root; App.tsx renders Navbar + Announcements above routed <main>.
- spa.html slimmed to SEO/config/footer shell (Jinja2 navbar/banners/theme script
  removed; #app is a plain div React fills).
- Backend: _build_config_json exposes system_announcement/network_announcement.

Tests (server-rendered nav/banner assertions → config):
- conftest.py: get_app_config() helper (robust __APP_CONFIG__ extraction).
- test_features/app/home/pages/dashboard rewritten to assert __APP_CONFIG__;
  dashboard client-rendered-stats tests replaced with a shell assertion.

Docs: REACT_MIGRATION.md Phase 5 (incl. deliberately-skipped react-query/
Storybook/Playwright), AGENTS.md Frontend section.

Verified: tsc clean, npm run build, vitest (49 passed), pytest tests/test_web
(251 passed), full suite (1459 passed), pre-commit (passed).
2026-07-21 20:06:52 +01:00
Louis King a5fabf7d46 chore(web): remove lit-html fallback & legacy code — Phase 4
The React frontend is complete (Phases 1-3); the lit-html fallback is dead
(its vendor globals were removed in Phase 3). Delete it and the scaffolding:

- Delete the entire src/meshcore_hub/web/static/js/spa/ lit-html tree,
  LitBridge.tsx, and legacy.d.ts.
- Remove the @legacy alias from vite.config.ts and tsconfig.json.
- Remove lit-html and qrcodejs from package.json (both unused now).
- Remove the lit-html fallback {% else %} branch from spa.html — the Vite
  build is now required to serve the UI (no fallback bundle).

Tests (fallback no longer exists):
- test_home/advertisements/nodes/messages.py: assert the React mount point
  (id="app") instead of a bundled-or-fallback script tag.
- test_caching.py: JS-cache tests are header-only (static JS is bundled into
  dist/, absent in test env; the middleware sets headers on 404 too); the
  dist-bundle HTML test drops its fallback branch.

Docs:
- AGENTS.md: new Frontend (React) section (host-run npm/vite/tsc toolchain,
  react-chartjs-2/react-leaflet/react-qr-code, CSS load order); clarified the
  compose-stack rule to exempt frontend tooling.
- REACT_MIGRATION.md: Phase 4 complete, final file structure, decisions.

Verified: tsc --noEmit clean, npm run build, full pytest (1463 passed,
22 skipped), pre-commit (passed).
2026-07-21 19:13:01 +01:00
Louis King 715659607a feat(web): React charts/maps/QR — Phase 3
Replace all window.Chart / window.L / window.QRCode globals and the
charts.js helper script with bundled React components:

- react-chartjs-2: typed config builders in utils/charts.ts
  (buildLineChart, buildActivityChart, buildStackedBar, buildRoutesTrend,
  buildRouteDetailStrip + ChartColors, averageRouteTier, routeQualityToTier)
  and wrappers in components/charts/Charts.tsx (ActivityChart,
  TrendLineChart, StackedBarChart, RoutesTrendChart, RouteDetailStrip).
  utils/charts.ts imports chart.js/auto. Wired into Home, Dashboard, Routes.
- react-leaflet: MapPage rewritten with MapContainer/TileLayer/Marker/Popup
  + a useMap MapController for fit-bounds and memoized markers; NodeDetail
  static hero map with divIcon marker + OffsetCenter. Both import
  leaflet/dist/leaflet.css.
- react-qr-code: replaces window.QRCode in Channels and NodeDetail.

Bundling & shell:
- Chart.js, Leaflet (+CSS), react-qr-code now bundled by Vite; removed the
  leaflet/chart.js/qrcodejs vendor <script>/<link> tags from spa.html and
  their copy steps from build.js (fonts stay vendored). Deleted charts.js.
- Moved the Vite CSS bundle (asset_app_css) into <head> before app.css so
  app.css dark-mode Leaflet overrides win over the bundled leaflet.css.
- Dropped the chart globals from the Window type declaration.

Tests/docs:
- test_caching.py: removed charts.js-specific tests; generic JS-cache tests
  now target spa/app.js.
- Updated charts.js cross-references in collector/routes.py + test_routes.py
  to point at spa-react/utils/charts.ts.

Verified: tsc --noEmit clean, npm run build (153 modules),
pytest tests/test_web (255 passed), pre-commit (passed).
2026-07-21 19:03:04 +01:00
Louis King 6cd14a58aa feat(web): React frontend scaffolding — Phase 1
- Add Vite 6 + TypeScript build replacing esbuild, React 19, React Router 7
- LitBridge wraps unconverted lit-html pages inside React app lifecycle
- Shared components: SortableTable, Pagination, FilterForm, StatCard, JsonTree,
  NodeDisplay, ObserverBadges, RouteTypeBadge, icons, ErrorBoundary, Alerts
- Hooks: useAutoRefresh, usePageTitle; utils: api, format, clipboard
- i18n via react-i18next mirroring existing translation keys
- Native React pages: NotFound, Maintenance; all other routes via LitBridge
- Jinja2 shell (spa.html) preserved for navbar, SEO, vendor globals, theme
- build.js generates legacy-compatible assets.json from Vite manifest
- REACT_MIGRATION.md documents full plan and conversion patterns
2026-07-21 10:21:39 +01:00
Louis King 510612d69b feat(web): adopt self-hosted IBM Plex Sans and IBM Plex Mono typography
Replace the OS system font stack with IBM Plex: the variable-weight
sans (100-700, one file per subset) for UI and headings, and Plex Mono
400 for the public keys, packet hashes, and hex that font-mono renders
across the app. Latin + latin-ext subsets only (shipped locales are
en/nl); no italics; mono is never rendered bold here.

Fonts are self-hosted from @fontsource packages via the existing
build.js vendor pipeline (no CDN), copied to static/vendor/fonts/ with
a hard build failure on wrong filenames. Wiring:

- input.css: @theme --font-sans/--font-mono + @font-face rules with
  unicode-ranges taken verbatim from the package CSS; Tailwind v4
  derives the document default from --font-sans, re-fonting daisyUI
  components with no other changes.
- spa.html: preload the latin sans variable woff2 (crossorigin, URL
  identical to the @font-face src) to minimize FOUT.
- charts.js: Chart.defaults.font.family to match (Chart.js otherwise
  uses Helvetica/Arial).
- error.html: name-prepend only; the page stays dependency-free.
- middleware.py: long-term immutable cache for /static/vendor/fonts/
  (stable names referenced from CSS, so ?v= versioning can't apply),
  with a matching test.
- app.css: slight hero-title letter-spacing tightening for Plex at
  display sizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:58:58 +01:00
renovate[bot] 3bcf691a5a chore(deps): update dependency esbuild to ^0.28.0 2026-05-05 17:41:36 +00:00
Louis King 3057a4841b feat: add esbuild bundling with content-hash cache busting
Bundle SPA JavaScript with esbuild for production builds, generating
content-hashed filenames for immutable caching. Vendor assets (Leaflet,
Chart.js, QRCode.js) get SHA256-based query params. Locale JSON files
get a combined hash version. Falls back to unbuiltsources when dist/
is absent.
2026-05-05 18:27:41 +01:00
Louis King 4f202c068b Replace all CDN dependencies with locally built vendor assets
- Add Node.js 22 LTS frontend build stage (package.json, build.js)
- Build Tailwind CSS v4 + DaisyUI v5 via CLI instead of runtime CDN
- Vendor lit-html, Leaflet, Chart.js, QRCode.js locally
- Update Dockerfile with multi-stage Node.js frontend build
- Migrate all DaisyUI v4 CSS variables to v5 syntax in app.css
- Fix dashboard grid layout (static class names for Tailwind JIT)
- Fix map popup transparent background (v5 variable rename)
- Fix leaflet.js.map 404 (include source maps in vendor copy)
- Fix custom page button contrast (remove btn-neutral)
- Replace table-compact with table-sm (DaisyUI v5 rename)
2026-04-20 13:16:13 +01:00