Skip to main content

Architecture Overview

This is the system map. After reading it you should be able to point to any feature, integration, or module and know roughly where its code lives.

Pair this page with the module boundary rules in ../../CLAUDE.md — the rules tell you what's allowed; this doc tells you what exists.


1. System Map

                       ┌─────────────────────────┐
│ Firebase Auth │
└────────────┬────────────┘
│ ID tokens
┌────────────────────────────┼────────────────────────────┐
│ │ │
┌──────┴───────┐ ┌─────────┴────────┐ ┌────────┴────────┐
│ Web app │ │ Cloud Functions │ │ Mobile app │
│ (pivot) │ HTTPS │ backend │ HTTPS │ (PIVOT-Mobile) │
│ React 18 SPA │ ──ky──▶ │ Hono routers │ ◀──ky── │ RN 0.77 bare │
│ │ bearer │ (modular monolith)│ bearer │ │
└──────┬───────┘ └─────┬────────────┘ └────────┬────────┘
│ │ │
│ Firebase JS SDK 8 │ Firebase Admin │ @react-native-firebase v21
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ RTDB ─── Firestore ─── Cloud Storage ─── FCM ─── Cloud SQL (PG) │
│ │
│ Pub/Sub ─── Cloud Scheduler ─── Secret Manager │
│ │
└──────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────┐
│ External integrations │
│ (POS, payroll, Stripe, │
│ OpenTable, SMTP, Slack, │
│ Rollbar, weather, …) │
└─────────────────────────────┘

Both clients authenticate against Firebase Auth, then call the backend over HTTPS with the ID token attached as Authorization: Bearer <token>. The backend verifies the token via Firebase Admin in middleware. Both clients also subscribe directly to RTDB for real-time updates (chat, schedule changes, presence) — those reads are gated by ../../database.rules.json.

See external systems for what PIVOT talks to.


2. Web Frontend (pivot/src/)

Bootstrap chain

src/index.tsx                ← Firebase init, store, providers
└─ <SessionProvider> ← Zustand session (user, role, company)
└─ <Provider> ← Redux store (legacy)
└─ <App> ← src/App.js (462 lines)
└─ <BrowserRouter>
└─ <RouteWithLayout> + <PrivateRoute>
└─ feature pages (lazy-loaded)

Key files:

State management — dual stack

The web app is mid-migration from Redux to Zustand:

StoreLives inOwns
Redux (legacy)../../src/store/, ../../src/actions/employees, companies, current company, dashboard, notifications, login user, modals, i18n
Zustand session../../src/auth/store/session.store.tsuser, employments, role, current company/employee/settings, presence, head-office impersonation (persisted to sessionStorage)
Zustand requests../../src/modules/requests/store/request list + counts
Zustand integrations../../src/modules/integrations/store/integration status

Rule of thumb: new features use Zustand. Redux stays for back-compat. See decisions.md.

Feature module pattern

Per CLAUDE.md, every feature in ../../src/routes/ follows:

routes/{Feature}/
├── index.tsx ← shell: wires sub-components, no logic
├── {Feature}Context.tsx ← shared state (only if multi-component)
├── hooks/ ← feature-specific logic + Firebase I/O
├── services/ ← feature-specific async functions
├── components/{X}/ ← dumb UI (no Firebase, no useSelector)
├── utils/ ← pure helpers
├── types.ts ← feature types
└── constants.ts ← enums, magic strings

Representative features:

FeatureEntryNotes
PayrollNew../../src/routes/PayrollNew/Tabs: Hours / Tips / Cuts. 8 export-format generators. Mega PayrollContext.tsx — see fragile-areas.md.
SchedulePage../../src/routes/SchedulePage/Daily / weekly / review views. Some giant components.
Employees../../src/routes/Employees/Table + ProfileModal (~2.5K lines).
Billing../../src/routes/Billing/Stripe Elements; calls backend Stripe handlers.
CompanySettings../../src/routes/CompanySettings/Form-driven Firebase writes.
Requests../../src/modules/requests/Zustand store; types: Availability, TimeOff, Exchange, …
Integrations../../src/modules/integrations/Connection flows for POS / reservations.

Shared layer

FolderContents
../../src/components/ui/Select, Input, SearchInput, TimeInput, OutlineButton, Loader*, TimePicker, DayPicker, MentionInput, …
../../src/hooks/useLocaleFromUrl, useNotifyAgain, usePopoverCloseOnScroll
../../src/services/company.service.ts, employeeDocuments, posSync.service, realtime/, http.client.ts (ky + ID token bearer auto-injection)
../../src/styles/theme.ts (palette), main.scss, feature SCSS partials
../../src/utils/date/format/validation pure functions
../../src/i18n.ts~10K translation keys (en + fr)
../../common/DTOs shared with backend (@common/dtos, @common/entities)

Path aliases

Defined in ../../tsconfig.json: @components/*, @/*, @utils/*, @common/*.


3. Backend (pivot/functions/)

A modular monolith — one Firebase Cloud Functions deployment, with internal module boundaries enforced by directory structure and manual DI. See decisions.md for the why.

Module inventory (15 modules)

All under ../../functions/modules/:

ModulePurpose
ai-memoryLLM memory/rules per company/employee. Uses @anthropic-ai/sdk.
applicantsJob applicant tracking (workflows, statuses).
attendanceClock-in/out punches, RTDB-backed.
chatGroup messaging, real-time sync.
companiesCompany profiles, settings, roles, sandbox, branch sync.
docsDocument API (minimal).
documentsLegal docs, signatures, e-sign workflows.
employeesEmployee data, integration IDs, payroll source.
integrationsIntegration metadata / registry.
payrollSettings, tip distribution, attendance anomaly detection.
pos-syncMaps POS employees ↔ Pivot employees (cache layer).
postsSocial-feed posts, media, mention notifications.
requestsShift/time-off/swap state machine. Large (~1.1K handler lines).
salesSales analytics from POS (invoices, employees).
scheduleShifts, drafts, templates, head-office cascades.

Per-module structure

functions/modules/<name>/
├── types/ ← pure data shapes
├── logic/ ← pure functions (no I/O)
├── contracts/ ← repository / service interfaces
├── handlers/ ← orchestration (logic + contracts)
├── repositories/ ← RtdbXxxRepository, PgXxxRepository, …
├── services/ ← external API / FCM / email implementations
├── endpoints/
│ ├── apis/ ← Hono sub-routers, one main router.ts
│ ├── events/ ← Pub/Sub / RTDB / Firestore triggers
│ └── scheduled/ ← Cloud Scheduler v2 cron
├── container.ts ← wires contracts → concrete implementations
└── index.ts ← module entry point

The strict layer-import rules and naming conventions live in CLAUDE.md.

Endpoint trace (example: list attendance punches)

  1. Router ../../functions/modules/attendance/endpoints/apis/attendance.router.ts — Hono route + zod validation
  2. Handler ../../functions/modules/attendance/handlers/ — orchestration, dependency-injected via closure
  3. Contract ../../functions/modules/attendance/contracts/AttendanceRepository interface
  4. Repository ../../functions/modules/attendance/repositories/RtdbAttendanceRepository implementation
  5. Wiring ../../functions/modules/attendance/container.tsnew RtdbAttendanceRepository() + listPunchesHandler({ attendanceRepository })

Middleware applied on every request: withCorstraceMiddlewareauthMiddlewareloggerMiddlewareerrorHandler.

Cross-cutting

FolderWhat lives there
../../functions/shared/middleware/auth, trace, logger, error-handler, company-scope, firebase-auth, …
../../functions/shared/infrastructure/logger.ts (structured JSON), pg-client.ts (Kysely + Cloud SQL Connector), hono-adapter.ts, errors.ts (AppError enum), request-context.ts (AsyncLocalStorage for trace/uid/logger)
../../functions/shared/types/pagination, result types, event payloads (cross-module)

Legacy zones (predate the FSD layout — be careful)

FolderWhat it holds
../../functions/db/RTDB / Firestore listener bag — incl. pg-sync/ (RTDB → Postgres one-way listeners).
../../functions/http/Legacy HTTP endpoints (clock-in-out, get-company-posts, …).
../../functions/on-call/One-off callables (print-pdf, copy-company, labor-calc, sales-calc).
../../functions/cron/13 legacy crons (9 .ts + 4 .js), mostly v1 functions.pubsub.schedule(). New crons go in modules/<x>/endpoints/scheduled/.
../../functions/schedule/Upcoming-shift push cron, separate from the schedule module.
../../functions/services/Global service registry (notifications, mailer, encryption, realtime channel).
../../functions/integration-engine/Provider abstraction layer for POS / reservations — see external systems.
../../functions/integrations/Per-provider implementations (Lightspeed, Clover, Givex, Cluster, Libro, Myr, Nethris, …).
../../functions/systems/stripe/Stripe webhook + subscription mgmt.

Build & deploy

  • Build: ../../functions/build.js — esbuild per-entry-point bundling. Each endpoints/apis/router.ts, endpoints/events/*.ts, endpoints/scheduled/*.ts becomes a separate Cloud Function. Post-build path-alias rewrite.
  • Entry: ../../functions/index.js — exports all functions (still has legacy direct exports alongside module exports).
  • Deploy: npm run deploy-staging-func from repo root; production via Firebase CLI.

Secrets & config

  • Secrets enum: ../../functions/get-secret.ts — type-safe accessor; ~56 entries cached at cold start from GCP Secret Manager.
  • New code: prefer defineSecret() from firebase-functions/params (see integrations/givex/secrets.ts pattern).
  • Non-sensitive: per-env functions/.env.{default,development,production} — see ../../CLAUDE.md → "Environment Variables & Secrets" for the intended config() wrapper pattern (target, not yet adopted everywhere).
  • Never use raw process.env in new code.

4. Mobile (PIVOT-Mobile/)

A bare React Native app (not Expo) targeting iOS & Android. Aimed at on-floor staff — clock in/out, view schedule, exchange shifts, chat, and (for managers) approve requests.

Tech stack

  • RN 0.77.2, native projects in ios/ and android/
  • Navigation: react-router-native v6 — chosen to mirror the web app's mental model (see decisions.md)
  • State: Redux (auth, employees, notifications, locale) + Zustand stores under src/stores/ for UI-local state
  • Firebase: @react-native-firebase/{auth,database,functions,messaging,storage} v21
  • HTTP: src/services/httpClient.ts — ky-based, mirrors web's bearer-token pattern
  • i18n: Lingui v5, JSON catalogs split by feature × locale under src/locales/{en,fr}/
  • Notifications: FCM backend + @notifee/react-native for local display, badge management, mute respect
  • Error tracking: Rollbar

Screens (17, under PIVOT-Mobile/src/routes/)

Attendance (clock-in/out + history, GPS-validated, QR via vision-camera), SchedulePage (calendar, availability, time-off, shift exchanges), Messages, GroupsNew (social posts), Group, Manager (manager-only views — requireManager gate), Dashboard, Profile (CV, documents), Sales, StaffOnFloor (Mapbox), WeatherRadar, Notifications, Libro, MediaGallery, SPLH, LaborCost, Auth (sign in/up, password reset, invitations).

Native features

GPS / geolocation (clock-in distance check), vision-camera (QR for punch), Mapbox, photo library + image-crop-picker, FCM push, biometrics-adjacent permissions, haptic feedback, deep linking via peopleapp:// URL scheme.

Build & release

  • Three Firebase project configs in PIVOT-Mobile/ios/firebase/ (dev / qa / prod) with separate bundle IDs.
  • Fastlane lanes (PIVOT-Mobile/fastlane/Fastfile): dev → TestFlight, beta → TestFlight, release → App Store.
  • GitHub Actions workflows under PIVOT-Mobile/.github/workflows/: {ios,android}-{dev,qa,production}-build.yml.
  • Manual steps in PIVOT-Mobile/BUILD_COMMANDS.md.

5. Three End-to-End Walkthroughs

A. Manager creates a shift on web → staff sees it on mobile

Web: SchedulePage UI
├─ src/routes/SchedulePage/services/* writes to RTDB
│ path: WeeklySchedule/{companyId}/{weekKey}/{shiftId}

└─ Backend: functions/db/pg-sync/on-schedule-write.listener.ts
picks up the RTDB write
└─ syncs to Cloud SQL via Kysely
(so reports / analytics can query Postgres later)

Mobile: PIVOT-Mobile/src/routes/SchedulePage
└─ @react-native-firebase/database subscription on the same path
fires; UI updates in real time

B. Staff clocks in on mobile

Mobile: src/routes/Attendance/ClockIn
├─ vision-camera scans QR
├─ @react-native-community/geolocation validates distance
└─ src/utils/api.ts → httpsCallable

Backend: functions/modules/attendance/endpoints/apis/attendance.router.ts
├─ authMiddleware verifies ID token
├─ traceMiddleware tags traceId
└─ createPunchHandler → AttendanceRepository → RTDB write
path: Attendance/{companyId}/{date}/{punchId}

Cron: functions/cron/late-for-shift-notification.ts
└─ may fire push via FCM if no punch by shift start

C. Manager runs a payroll export on web

src/routes/PayrollNew/PayrollContext.tsx
├─ subscribes to AttendanceSettings/{companyId}/... (RTDB)
├─ subscribes to PosSync/{companyId}/... (RTDB)
├─ aggregates hours / tips / cuts in-memory
│ (calculations in src/routes/PayrollNew/utils/)
└─ user picks export format → one of:
src/routes/PayrollNew/export-formats/
├─ generate-powerpay-export.ts
├─ generateAcombaExport.ts
├─ generateByShiftExport.ts
├─ generateByWeekExport.ts
├─ generatePayevolutionExport.ts
├─ generatePayrollExport.ts
├─ generateQuickBooksOnlineExport.ts
└─ generateQuickBooksTimeExport.ts
→ produces CSV / Excel client-side, no backend round-trip

6. Cheat-Sheets

Web src/ top level

src/
├── App.js ← router shell
├── index.tsx ← bootstrap
├── config.ts ← Firebase config
├── i18n.ts ← all translation keys
├── store/ ← Redux (legacy)
├── actions/ ← Redux thunks
├── auth/store/ ← Zustand session store
├── modules/ ← Zustand-first feature modules (requests, integrations)
├── routes/ ← FSD-style features (most code lives here)
├── components/ui/ ← shared UI primitives
├── hooks/ ← shared hooks
├── services/ ← shared services (incl. http.client.ts)
├── styles/ ← theme.ts, SCSS partials
├── utils/ ← pure helpers, route guards
└── types/ ← global types

Backend functions/ top level

functions/
├── index.js ← exports all functions
├── build.js ← esbuild config
├── get-secret.ts ← typed secret enum
├── tsconfig.json
├── modules/ ← FSD modular monolith (15 modules)
├── shared/ ← middleware, infrastructure, types, logic
├── integrations/ ← per-POS / payroll / reservation provider impls
├── integration-engine/ ← provider abstraction (BaseProvider/…)
├── systems/stripe/ ← Stripe webhook + subscription mgmt
├── db/ ← legacy RTDB/Firestore listeners + pg-sync
├── http/ ← legacy HTTP endpoints
├── on-call/ ← legacy callables
├── cron/ ← legacy v1 Pub/Sub crons
├── schedule/ ← legacy upcoming-shift push cron
└── services/ ← global service registry

Mobile PIVOT-Mobile/src/ top level

src/
├── index.js (root) ← Firebase messaging, deep links, fonts
├── createStore.js ← Redux store factory
├── Router.tsx ← React Router Native
├── routes/ ← 17 screens
├── components/ ← shared UI
├── services/ ← HTTP + Firebase wrappers
├── stores/ ← Zustand (UI-local)
├── reducers/ ← Redux
├── actions/ ← Redux thunks
├── auth/ ← AuthService + invitation store
├── hooks/
├── utils/ ← theme, time format, RTDB wrapper, rollbar
├── contexts/
├── locales/{en,fr}/ ← Lingui catalogs
├── config/i18n.ts ← Lingui setup
└── types/

What Next

→ External systems: external-systems.md → The "why" for the choices above: decisions.md → Land mines: fragile-areas.md