Stack
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui |
| Backend | Node.js, Express, tRPC |
| Database | TiDB (MySQL-compatible), Drizzle ORM |
| Resend (transactional), custom HTML templates | |
| Auth | Session-based, workspace-scoped RBAC |
| Infra | Docker Compose (MySQL + Node Alpine) |
| Calendar | Cal.com (webhooks for demo booking) |
Module map
client/src/
├── pages/ ← one file per route
├── components/ ← shared UI components
├── features/ ← self-contained feature modules (controlTower, etc.)
└── lib/ ← utilities (i18n, formatting, auth)
server/
├── routers/ ← tRPC procedure routers (one per domain)
├── jobs/ ← scheduled background jobs
├── modules/ ← complex domain modules (recruitment, etc.)
├── _core/ ← Express app init, middleware, job scheduler
├── email.ts ← all transactional email templates
└── demoWebhooks.ts ← Cal.com webhook handler
RBAC model
Roles (in ascending permission order):
Company roles (on company_members):
| Role | Access |
|---|---|
company_member |
Own / department / team data per visibility scope |
hr_admin |
HR data within the company (employees, attendance, documents) |
finance_admin |
Billing, payroll, WPS within the company |
reviewer |
Compliance read-only |
external_auditor |
Read-only; blocked from all mutations |
client |
Client/buyer portal access |
company_admin |
Full company access |
Platform roles (global, on platform_user_roles): platform_admin, super_admin — cross-company + platform ops.
Mutations gate on requireWorkspaceMutation / requireHrOrAdmin — never bare requireWorkspaceMembership for writes.
Workspace model
Each company has one workspace. Users belong to a workspace via company_members. The companies.subscription_status column (legacy) and company_subscriptions table must stay in sync — see Migration Apply SOP.
Internationalisation
The platform is bilingual: English (en-OM) and Arabic (ar-OM), with RTL layout for Arabic. Locale files live in client/src/locales/. All numeric formatting uses Western digits (nu-latn) even in Arabic locale, per fmtLocale.ts convention.
Brand
Primary: forest green #006948 — defined in client/src/index.css as CSS custom properties. Email templates mirror the hex values directly (emails can't read CSS vars). The SmartProBrand component is the only approved way to render the logo in React — never hand-roll an SP badge.