Architecture overview
QA Sync is a single-page React application backed directly by Supabase. There is no application server of its own: the browser talks to Supabase Auth, the PostgREST API, database functions (RPCs) and Storage, and Postgres Row Level Security enforces every data boundary.
┌──────────────────────────┐│ Browser: React 19 SPA ││ (Vite build on Vercel) │└──────┬─────────┬─────────┘ │ │ ▼ ▼ Supabase Auth Supabase PostgREST / RPC ──▶ Postgres (RLS) Supabase Storage ──────────▶ private buckets| Layer | Technology |
|---|---|
| UI | React 19, TypeScript (~5.8), Tailwind CSS 4, lucide-react icons |
| Build | Vite 6 |
| Auth | Supabase Auth, email and password |
| Data | Supabase Postgres via @supabase/supabase-js, with RPCs for multi-step workflows |
| Files | Supabase Storage, private buckets with signed URLs |
| Authorisation | Postgres Row Level Security and SECURITY DEFINER helper functions |
| Hosting | Vercel (static SPA with a rewrite to /index.html) |
Source tree
Section titled “Source tree”apps/web/src/ api/ Supabase data access and mutations (the only data-access layer) components/ Reusable shared UI only; layout/ holds the app shell config/ Runtime feature and environment gates constants/ Shared workflow and status constants hooks/ Workspace data and actions: admin/, app/, developer/, tester/, analytics/ lib/ Low-level helpers routes/ The in-house router screens/ Route-level screens and role-owned workspace UI utils/ Domain-specific pure helpers types.ts Shared domain types (roles, statuses, entities) main.tsx Entry point App.tsx Thin route shell
supabase/ migrations/ Schema and policy history seed.sql Local seed data config.toml Local ports and auth settingsRouting
Section titled “Routing”QA Sync uses a small in-house history router in apps/web/src/routes/index.ts, not react-router-dom. The route set is fixed:
The app is served under /app (Vite base), so in the browser each route below is prefixed: /app/login, /app/admin, and so on. The router strips the prefix, so route names stay the same in code.
| Route | Screen |
|---|---|
/login |
Login |
/access-pending |
Access Pending |
/admin |
Admin workspace (Admin and Super Admin) |
/product-manager |
Product Release Workspace |
/developer |
Developer Workspace |
/tester |
Tester workspace |
/workspace |
Stakeholder workspace (UI Designer, Client, Viewer) |
apps/web/src/hooks/app/useAppSession.ts restores the Supabase session, resolves the profile, and redirects to the right route. apps/web/src/screens/RoleWorkspaceScreen.tsx wraps the role dashboard in the shared AppShell and lazy-loads it.
Session and access resolution
Section titled “Session and access resolution”- Supabase Auth signs the user in.
apps/web/src/api/profile-access.tsreads the user’s row inpublic.profiles.- If there’s no row, it calls
ensure_current_user_profile(), which creates or repairs the profile from the admin-managedpublic.access_directory. - An active profile with a role goes to its workspace; anything else goes to
/access-pending.
See Database and Row Level Security.
Data access boundary
Section titled “Data access boundary”All table, RPC and storage access lives in apps/web/src/api. Screens, hooks and components call apps/web/src/api functions; they never call Supabase directly. The single exception is Supabase Auth session calls (sign-in, sign-out, auth-state listener), which LoginScreen.tsx and useAppSession.ts make on the shared client exported from apps/web/src/api/supabase.ts.
API functions follow load* / save* / delete* naming and return a result envelope with source and message instead of throwing.
Role workspaces
Section titled “Role workspaces”| Role | Screen | Data hook(s) |
|---|---|---|
| Admin, Super Admin | screens/admin/AdminDashboard.tsx |
hooks/admin/useAdminDashboardData.ts, useAdminDashboardActions.ts, useAdminDashboardUiState.ts |
| Product Manager | screens/product-manager/ProductManagerDashboard.tsx |
hooks/analytics/useAnalyticsDashboard.ts |
| Developer | screens/developer/DevDashboard.tsx |
hooks/developer/useDeveloperDashboardData.ts |
| Tester | screens/tester/TesterDashboard.tsx |
hooks/tester/useTesterDashboardData.ts, useTesterDeviceProfile.ts |
| UI Designer, Client, Viewer | screens/stakeholder/StakeholderDashboard.tsx |
hooks/analytics/useAnalyticsDashboard.ts |
Shared panels used across workspaces live in apps/web/src/components: the release board (release/ReleaseControlBoard.tsx), notification centre, comments, history timelines, hierarchy tree and analytics panels.
Performance
Section titled “Performance”- Role dashboards, admin tabs and admin modals are lazy-loaded.
- The admin route loads a lightweight overview aggregate RPC first and fetches heavier tab data on demand.
- Reference data, admin lists, task requests, analytics and attachment signed URLs use in-memory caches with explicit force-refresh after mutations.
- The build emits
react-vendor,supabase-vendorandui-vendorchunks so the entry bundle stays small. - Charts are native SVG; there is no charting dependency.
- Local development doesn’t mount React
StrictMode, avoiding duplicate effect-driven fetches, and keeps the resolved auth snapshot across Fast Refresh.
Recorded decisions
Section titled “Recorded decisions”Architecture decisions are recorded one per file in docs/decisions/ in the repository. Accepted decisions are binding; read the ones that apply before changing an area. Highlights:
| Decision | Summary |
|---|---|
| 0001 | Use the in-house router, not react-router-dom. |
| 0002 | Admin charts are native SVG, not recharts. |
| 0003 | apps/web/src/api is the only frontend data-access boundary. |
| 0004 | Public self-sign-up stays disabled. |
| 0006 | RLS policies use SECURITY DEFINER helpers, not recursive self-queries. |
| 0008 | Shared UI in apps/web/src/components, role-owned UI in apps/web/src/screens. |
| 0009 | npm is the package manager. |
| 0014 | Delivery lifecycle and QA execution stay separate. |
Constraints
Section titled “Constraints”- The router has no nested route composition.
- There is no automated test suite;
npm run lint(type-check) and the validation scripts are the checks.