Files
familysync/apps/pwa
Lucas BergerandClaude Opus 4.8 b6490feff4
CI / changes (pull_request) Successful in 9s
CI / api (pull_request) Successful in 3m2s
CI / fast-checks (pull_request) Successful in 4m20s
CI / security (pull_request) Successful in 1m14s
CI / harness (pull_request) Successful in 6m56s
CI / gate (pull_request) Successful in 2s
fix(19): satisfy CI fast-checks + secret scan
Lint (eslint --max-warnings 0):
- index.ts: disable no-unsafe-argument on the type-only Context mismatch when
  delegating to the OIDC handler inside the local-session skip wrapper
- localAuth.ts: handleLogout is sync (no await) — drop async (require-await)
- devBypass.ts: disable detect-possible-timing-attacks on the public well-known
  dev-placeholder string compare (not a secret comparison)
- remove dead code / unused bindings flagged by no-unused-vars: makeTestApp
  (localSession.test), makeUnauthContext + BrowserContext import (login.spec),
  unused memberId (admin.test), unused txSelectCount counter (me.test)
- localAuthMiddleware.test / me.test: fix unused + reflow-detached
  eslint-disable directives

Format: prettier --write across the 20 Phase-19 files that were never formatted.

Secret scan (gitleaks): allowlist two false positives — the synthetic >=32-char
TEST_SECRET in localSession.test.ts, and .planning/ design prose (a generic-api-key
regex hit on "credential atomically, 409-equivalent"). Neither is a real secret.

Verified locally: format:check, lint, typecheck, md:lint, gitleaks (no leaks),
PWA 266/266, API 452/452.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:05:15 -04:00
..

@familysync/pwa

The React 19 PWA frontend for FamilySync. Delivers a single installable, low-friction interface showing the color-coded family calendar and shared collaborative lists, designed for a mixed Android/Apple household.

Part of the FamilySync monorepo.

Stack

Layer Library Version
UI framework React ^19.0.0
Build + dev server Vite 8.0.16
PWA service worker + manifest vite-plugin-pwa ^1.3.0
Calendar widget @schedule-x/calendar 4.6.0
Server state @tanstack/react-query 5.101.0
UI state zustand 5.0.14
Routing react-router ^7.17.0
iCalendar parsing ical.js 2.2.1
Drag-and-drop (lists) @dnd-kit/core + @dnd-kit/sortable ^6 / ^10

Development

Run the PWA dev server from the monorepo root:

pnpm --filter @familysync/pwa dev

The API backend must also be running for most features. See GETTING-STARTED.md for full stack bring-up instructions.

Scripts

Command What it does
pnpm --filter @familysync/pwa dev Start Vite dev server (HMR)
pnpm --filter @familysync/pwa build Type-check then build production bundle (tsc && vite build)
pnpm --filter @familysync/pwa preview Serve the production build locally
pnpm --filter @familysync/pwa typecheck Run tsc --noEmit without emitting files
pnpm --filter @familysync/pwa test Run Vitest test suite once (vitest run)

Source layout

src/
  api/          # Typed fetch wrappers for @familysync/api (client.ts, listsClient.ts)
  components/   # Shared UI components co-located with their *.test.tsx files
  hooks/        # Custom React hooks (useListSSE, usePushSubscription, useFocusTrap)
  lib/          # Pure helpers: calendarConfig, colorUtils, eventDateTime, hydrateEvents, loginRedirect
  routes/       # React Router route components with co-located tests (ListDetail, ListsIndex)
  store/        # Zustand stores: calendarStore, listsStore
  styles/       # Global CSS
  main.tsx      # App entry point — React Query client, router, global error handlers
  App.tsx       # Root component
  sw.ts         # Workbox service worker entry
  test-setup.ts # Vitest + jest-dom global setup

Routes and their co-located components own the feature slice. Library code (pure, side-effect-free) lives in lib/. Zustand stores hold UI-only state; server data is exclusively managed by TanStack Query.

Communication with the API

All backend calls go through src/api/client.ts and src/api/listsClient.ts. Key behavior:

  • credentials: 'include' on every request so the OIDC session cookie is forwarded.
  • redirect: 'manual' — a 401 or opaque redirect (the Authelia 302) is caught and thrown as a typed SessionExpiredError. The global QueryCache / MutationCache error handler in main.tsx intercepts this and shows the session-expiry interstitial.
  • Re-authentication requires a top-level navigation to /api/login (handled by src/lib/loginRedirect.ts), not a fetch redirect, because browsers block CORS redirects to an external IdP.
  • Live list updates are delivered via SSE through src/hooks/useListSSE.ts; the hook calls queryClient.invalidateQueries on each event so TanStack Query re-fetches.

In development the Vite proxy routes /api requests to the API server on port 3000, keeping cookies same-site. In production both apps are served same-origin via the Pangolin/Newt tunnel.

Testing

Tests are co-located with their source files (*.test.tsx / *.test.ts) and use React Testing Library + @testing-library/jest-dom. The test environment is jsdom.

# run once
pnpm --filter @familysync/pwa test

# watch mode (during development)
pnpm --filter @familysync/pwa exec vitest

No coverage threshold is configured. Run pnpm --filter @familysync/pwa typecheck separately — Vitest uses esbuild and will not surface TypeScript errors.

PWA install notes

  • The app must be added to the Home Screen on iOS for push notifications to work (iOS 16.4+ minimum).
  • Push permission must be requested inside a tap handler; calling pushManager.subscribe() on page load is blocked.
  • Background sync and background push are not supported on iOS; all push messages must display a visible notification.

Further reading