<!-- generated-by: gsd-doc-writer -->

# @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](../../README.md).

## 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:

```bash
pnpm --filter @familysync/pwa dev
```

The API backend must also be running for most features. See [GETTING-STARTED.md](../../docs/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

```text
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`.

```bash
# 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

- [Architecture overview](../../docs/ARCHITECTURE.md)
- [Getting started](../../docs/GETTING-STARTED.md)
- [Development guide](../../docs/DEVELOPMENT.md)
- [Testing guide](../../docs/TESTING.md)
