A multi-tenant B2B platform for space organisations to plan missions and intelligently assign crew based on skills, availability, and workload.
Core value: Define a mission's skill requirements and get intelligent, constraint-aware crew suggestions in seconds — replacing hours of manual cross-referencing.
| Layer | Technology |
|---|---|
| Framework | React Router 7 (framework mode, SSR) |
| Language | TypeScript 5.9 |
| Database | Prisma Postgres (managed) |
| ORM | Prisma 7 with tenant-scoped client extensions |
| Auth | BetterAuth with organization plugin |
| UI | shadcn/ui + Tailwind CSS 4 + Radix UI |
| Forms | Conform + Zod validation |
| Deployment | Vercel |
| Testing | Vitest + Playwright |
Every organisation is a fully isolated tenant. Data never leaks across organisations.
Three roles with server-enforced permissions:
- Directors — manage org settings, approve/reject missions, full visibility
- Mission Leads — plan missions, run the matcher, submit for approval (cannot approve own missions)
- Crew Members — manage own profile, set availability, respond to assignments
- Structured skill profiles with 1–5 proficiency levels (Novice → Expert)
- Organisation-scoped skill catalog managed by Directors
- Calendar-based availability with date ranges
- Assignment history showing past and current missions
Missions follow a state machine with role-gated transitions:
stateDiagram-v2
[*] --> Draft
Draft --> Submitted : Mission Lead submits
Submitted --> Approved : Director approves
Submitted --> Rejected : Director rejects
Rejected --> Draft : Mission Lead revises
Approved --> Active : Director activates
Active --> Complete : Director completes
Complete --> [*]
Each mission defines skill requirements (skill + minimum proficiency + crew count needed) used by the matching engine.
The matching engine scores and ranks crew against mission requirements using three weighted factors:
flowchart LR
M[Mission Requirements] --> E[Matching Engine]
C[Crew Pool] --> E
E --> R[Ranked Results]
subgraph Scoring
S[Skill Match] --- |proficiency vs requirement| Score
A[Availability] --- |date overlap with mission| Score
W[Workload Balance] --- |current assignment load| Score
end
E --> Scoring
Scoring --> R
- Transparent scoring — each result shows a per-factor breakdown so users understand the ranking
- Strategy pattern — the algorithm sits behind a
MatchingStrategyinterface, allowing new algorithms (ML, constraint-based) to be plugged in without changing consumers
Org-level operational metrics at a glance: active missions, pending approvals, crew utilization, and upcoming assignments. Views are role-scoped — Directors see the full picture, Leads see their missions, Crew see their schedule.
flowchart TB
Browser["Browser (React 19)"]
subgraph RR7["React Router 7"]
MW["Middleware\n(auth + tenant context)"]
Loaders["Route Loaders\n(server-side data)"]
Actions["Route Actions\n(server-side mutations)"]
end
subgraph Services["Server Services"]
Auth["BetterAuth\n(sessions, org membership)"]
MissionSvc["Mission Service\n(lifecycle, CRUD)"]
CrewSvc["Crew Service\n(profiles, skills)"]
Engine["Matching Engine\n(strategy pattern)"]
end
subgraph Data["Data Layer"]
Prisma["Prisma Client\n($extends per-request)"]
DB[(Prisma Postgres)]
end
Browser <--> RR7
MW --> Loaders
MW --> Actions
Loaders --> Services
Actions --> Services
Services --> Prisma
Prisma --> DB
| Decision | Rationale |
|---|---|
| Per-request tenant-scoped Prisma client | $extends injects orgId into every query automatically — developers can't accidentally leak data across tenants |
| React Router middleware for auth | Single enforcement point for session validation, org resolution, and RBAC. No loader handles its own auth. |
| Strategy pattern for matching | The weighted scorer is the v1 algorithm. The interface supports swapping in ML or constraint-based algorithms without touching callers. |
| Server-side RBAC enforcement | Client-side checks are UX sugar. All permissions enforced in loaders/actions. |
| Loader revalidation | After any mutation, RR7 automatically re-runs active loaders — no manual cache invalidation needed |
erDiagram
Organization ||--o{ Member : has
User ||--o{ Member : "belongs to"
Organization ||--o{ Skill : defines
Organization ||--o{ Mission : owns
Organization ||--o{ CrewProfile : has
CrewProfile ||--o{ CrewSkill : has
CrewProfile ||--o{ Availability : schedules
CrewProfile ||--o{ MissionAssignment : assigned
Skill ||--o{ CrewSkill : "proficiency in"
Skill ||--o{ MissionSkillRequirement : "required by"
Mission ||--o{ MissionSkillRequirement : requires
Mission ||--o{ MissionAssignment : staffed
CrewProfile {
string id PK
string orgId FK
string memberId
string bio
}
CrewSkill {
string id PK
string skillId FK
int proficiency "1-5"
}
Mission {
string id PK
string orgId FK
string name
date startDate
date endDate
enum status "DRAFT|SUBMITTED|APPROVED|..."
}
MissionSkillRequirement {
string skillId FK
int minProficiency "1-5"
int crewCount
}
All domain tables include orgId — the tenant-scoped Prisma client filters on this automatically.
- Node.js 20+
- A Prisma Postgres database (or any PostgreSQL instance)
npm installCreate a .env file:
DATABASE_URL="your-prisma-postgres-connection-string"
BETTER_AUTH_SECRET="your-random-secret"
npx prisma migrate dev # Apply database migrations
npm run dev # Start dev server at http://localhost:5173npm test # Unit tests (Vitest)
npm run test:e2e # E2E tests (Playwright)npm run build # Runs migrations, generates Prisma client, typechecks, builds
npm start # Serve production buildapp/
├── routes/ # React Router file-based routes
│ ├── _auth.*.tsx # Unauthenticated shell (login)
│ ├── _app.*.tsx # Authenticated shell (sidebar, tenant context)
│ └── api.auth.$.ts # BetterAuth API handler
├── services/ # Server-only domain logic (.server.ts)
│ ├── auth.server.ts # BetterAuth configuration
│ ├── db.server.ts # Prisma client + tenant scoping
│ └── permissions.ts # RBAC role definitions
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── auth/ # Sign-in, org creation forms
│ ├── crew/ # Crew cards, skills, availability
│ ├── missions/ # Mission forms, Kanban board, cards
│ └── layout/ # Sidebar, navigation
└── lib/ # Shared utilities
prisma/
└── schema.prisma # Database schema (18 models)
Test accounts are documented in .scratch/test-accounts.md.
| Organisation | User | Role | Password | |
|---|---|---|---|---|
| Test Org | Homer Simpson | Director | homer@test.com | 12345678 |
| Test Org | Marge Simpson | Mission Lead | marge@test.com | xwwSdwaKkroYeRoW |
| Test Org | Bart Simpson | Mission Lead | bart@test.com | #k8Tchqv%2C9jLmQ |
| Test Org | Maggie Simpson | Mission Lead | maggie@test.com | SDBQ@TN#nt@r3ge6 |
Phase 1 (Foundation) is complete — authentication, RBAC, tenant isolation, and the app shell are working.
| Phase | Status | Description |
|---|---|---|
| 1. Foundation | Complete | Multi-tenant auth, RBAC, tenant-isolated data access |
| 2. Crew & Missions | Not started | Crew profiles, skill management, mission lifecycle |
| 3. Matching Engine | Not started | Weighted crew-to-mission scoring with transparency |
| 4. Dashboard & Deploy | Not started | Metrics, seed data, UX polish, Vercel deployment |
- Crew skill profiles & availability calendar — schema exists, UI and service layer pending (Phase 2)
- Mission CRUD & lifecycle state machine — schema and enum exist, workflow logic pending (Phase 2)
- Auto-matching engine — architecture designed (strategy pattern), implementation pending (Phase 3)
- Operational dashboard & metrics — pending all domain data (Phase 4)
- Seed data — will populate realistic demo scenario once all models are in place (Phase 4)
- Vercel deployment — final phase after polish
- Email/push notifications — focus on core workflows
- Real-time collaboration / WebSockets — RR7 loader revalidation is sufficient
- OAuth / social login — email/password via BetterAuth is sufficient for v1
- Custom role creation — three fixed roles demonstrate the pattern without the complexity
- Billing / subscriptions — not relevant to the domain
- Mobile native app — responsive web design covers mobile use cases
See .planning/ROADMAP.md for the full phased roadmap with success criteria per phase.
Next up: Phase 2 — Crew profiles with structured skills, calendar-based availability, mission CRUD with skill requirements, and the approval lifecycle (Draft → Submitted → Approved → Active → Complete).