Phase 17 D-10 reworked /admin into a two-tab layout; the Timezone section moved into the (initially hidden) Settings tab panel. timezone-verify.spec.ts predates that rework and asserted the region visible on bare /admin, so all 7 tests timed out in the harness. Click the Settings tab in beforeEach and after the persist-test reload. Product behavior is correct; the spec was stale. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@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 typedSessionExpiredError. The globalQueryCache/MutationCacheerror handler inmain.tsxintercepts this and shows the session-expiry interstitial.- Re-authentication requires a top-level navigation to
/api/login(handled bysrc/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 callsqueryClient.invalidateQuerieson 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.