Files
familysync/apps/pwa
Lucas BergerandClaude Sonnet 4.6 ee04aee4fb fix(20): CR-02 derive editorMember from live query data
Store only the member id in editorMemberId state rather than the full
AdminMember object. Derive editorMember from membersQuery.data after each
render, so query invalidations following a per-section save always give
MemberEditorSheet fresh server state — eliminating the stale-reference
silent-overwrite race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:00:47 -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 lint Run ESLint over src/ and e2e/ with zero warnings allowed
pnpm --filter @familysync/pwa typecheck Run tsc --noEmit for both src/ and e2e/ tsconfigs
pnpm --filter @familysync/pwa test Run Vitest unit/integration suite once (vitest run)
pnpm --filter @familysync/pwa test:e2e Run Playwright end-to-end tests headlessly
pnpm --filter @familysync/pwa test:e2e:ui Open the Playwright UI runner
pnpm --filter @familysync/pwa test:e2e:headed Run Playwright tests in a headed browser

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 (AdminPage, ListDetail, ListsIndex, LoginPage, SetupPage)
  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

Unit and integration 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

End-to-end tests live in the e2e/ directory and run with Playwright (@playwright/test 1.60.0). They cover login, calendar, lists, layout, admin, and timezone verification flows.

# headless
pnpm --filter @familysync/pwa test:e2e

# with Playwright UI
pnpm --filter @familysync/pwa test:e2e:ui

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