PeopleSphere — Frontend Component & Page Plan

Atomic component discipline, the Page → Layout → Skin → Theme composition model, and the full screen inventory (auth, core, and Healthcare's built Core OPD module) with proposed routes and endpoints.

Status: Draft — companion to surfaces.md, access-model.md, 10-BACKEND.md. Standing in for the real ui/ app until the frontend stack is chosen and this placeholder is replaced with actual routed pages.

1.Composition model

Four layers, each only extending the one below it. Nothing below "Skin" is ever tenant-aware; nothing above "Component behavior" is ever business-logic-aware.

ComponentStructural building block only — markup/layout, no branding, no tenant knowledge. e.g. Button, StatCard, TableList, Modal.
↓ behavior binds to structure, still tenant-blind
Component behaviorInteraction/logic owned by a component, kept separate from its look — search/filter/paginate, live polling, validation. Identical regardless of who's skinning it.
↓ a skin dresses components, never rewrites them
SkinTenant- or domain-level identity: branding, terminology strings, which modules/fields show. e.g. a "Hope Clinic" skin, or a shared "Healthcare" skin reused by several clinic tenants.
↓ a theme is a variant inside one skin, not a new identity
Skin themeVisual-token variant within a skin — light/dark, a seasonal palette. Same logo, same terminology, same module set as the parent skin; only tokens (color, density) change.

Page / Layout wiring

A Page owns data-fetching and composes Organisms/Templates into its own content, then hands that content to Layout as children. Layout owns chrome and skin/theme resolution — a Page never reads skin or theme directly.

Page (e.g. LoginPage, DashboardPage, ConsultPage)
  → fetches its own data
  → composes Templates/Organisms into `children`
  → renders <Layout>{children}</Layout>

Layout (e.g. AuthLayout, AppLayout)
  → resolves activeSkin   (from subdomain → organisation → domain)
  → resolves activeTheme  (nested under activeSkin; user/session preference or skin default)
  → provides both to everything it renders via context (tokens + terminology strings)
  → renders NavShell / PrimaryNav (AppLayout only) + {children}
Rule: a Component or its behavior must never import a skin or tenant value directly — it only reads tokens/strings handed down through Layout's context. That's what makes "provision skins now, design them later" possible without touching components when a second tenant shows up.

2.Component inventory

Derived from the patterns already repeated across Hope Clinic's built screens — these are the reusable pieces every domain's screens should be assembled from, not redesigned per module.

Atoms — smallest building blocks
ComponentNotes
ButtonPrimary / secondary / danger variants (e.g. "Create staff account", "Deactivate", "Submit prescription · Patient OUT").
Input / Select / DateInput / CheckboxBare form controls, label rendered by the FormField molecule, not the atom itself.
StatusPillColored short label — WAITING / IN / OUT / DONE, ACTIVE / CANCELLED, Coming Soon.
BadgePlan/entitlement badges — "Core OPD — Included", "Optional Add-on — Coming Soon".
Icon, Avatar/RoleTag, LinkTabNav and identity primitives.
Molecules — small combinations
ComponentNotes
FormFieldLabel + input + inline error. Every form on every screenshot uses this shape.
SearchBarFilter input + Search + Clear/Show all — Patient List, Visit History, Patient History, Front Desk lookup.
StatCardCount + label, color-coded — the unit that composes into StatBar.
FilterRowMulti-field filter (patient/year/month + Apply/Clear) — Visit History.
ModuleTileTitle + entitlement badge — one cell of the dashboard's tile grid.
TableRow / PaginationControlRow rendering + "Previous / Page X of Y / Next" — identical across every list screen.
Organisms — composed, screen-sized pieces
ComponentComposed ofBehavior owned
NavShellLogo/org name, user name + role badge, Log outSession display, logout action
PrimaryNav / TabNavLinkTab listActive-route highlighting, permission-based visibility
StatBarStatCard × NLive counts (Waiting/In/Out/Done) — Dashboard, Front Desk, Doctor Consultation all reuse this exact organism
TableListSearchBar/FilterRow + count line + table + PaginationControlSearch, filter, paginate — Patient List, Visit History, Staff Accounts, Medicine Catalogue all reuse this shape
ModuleTileGridModuleTile × NReads entitlement state, renders enabled vs. "Coming Soon"
VisitHistoryModalModal + FilterRow + status listPer-patient visit drill-down, audit-logged open event
MedicineLineEditorRepeatable FormField row (Medicine/Dosage/Frequency/Duration/Timing) + Add/RemoveDynamic row add/remove, per-field "Print" toggles
ConsultationFormSymptoms/Diagnosis/Examination/Reports fields + MedicineLineEditor + Follow-up + Investigations/AdviceSave draft vs. Submit-and-mark-OUT, previous-consultations copy-forward
Templates & Layouts
ComponentUsed by
AuthLayoutLogin, Password Recovery, Password Help, Change Password — centered card, no PrimaryNav, still skinned (org logo resolved from subdomain before login)
AppLayoutEvery authenticated page — NavShell + PrimaryNav + content slot, resolves activeSkin/activeTheme
DashboardTemplateDashboard — StatBar (optional, permission-gated) + ModuleTileGrid
ListDetailTemplatePatient List, Visit History, Staff Accounts, Medicine Catalogue, Patient History
QueueConsultTemplateFront Desk (Today's Visits & Tokens), Doctor Consultation queue + detail

3.Skin & theme provisioning

Build one real skin now (Hope Clinic, Healthcare domain) against actual data. Don't design a second skin speculatively — just make sure adding one is a config change, not a component change.

LayerAllowed to overrideNever touches
SkinLogo, org/brand name, accent color default, terminology strings (Patient/Visit/Prescription), which modules render in the tile gridComponent markup, component behavior, layout structure
Skin themeColor tokens, light/dark mode, densityBranding/logo, terminology, module set — those belong to the parent skin, not the theme

4.Auth experience

One login per organisation, resolved from the wildcard subdomain (surfaces.md §5.1) — no separate admin/employee login. Uses AuthLayout.

ScreenRouteStatusWhoUser storyEndpoint(s)Purpose
Login /account/login proposed Public, pre-auth As clinic staff, I sign in with username/password to reach my role's dashboard. POST /api/v1/auth/login Resolve org from hostname (ORGANISATION_NOT_FOUND if unrecognized), authenticate, issue JWT (roles/permissions/scopes claims).
Password Recovery /account/password-recovery proposed Public, pre-auth As a user who forgot my password, I request a reset link/code. POST /api/v1/auth/password-recovery Self-service reset initiation, scoped to the resolved organisation.
Password Help /account/password-help proposed Public, pre-auth As a user stuck on reset, I want plain guidance or a contact path. Static content — no endpoint Fallback support content when self-service reset isn't enough.
Forced Change Password /account/change-password proposed Authenticated, must_change_password As a new staff member given a temporary password, I must set my own before using the app. POST /api/v1/auth/change-password Security gate — blocks dashboard access until cleared (Administration screenshot shows "Temporary password" issuance that requires this).

5.Core — always-on, role-agnostic

Per surfaces.md §2.2 — not entitlement-gated, available to every organisation regardless of subscribed modules. Only Dashboard has a built screenshot today; the rest are spec-only.

ScreenRouteStatusPermissionUser storyEndpoint(s)Purpose
Dashboard / built Authenticated, all roles As any staff member, I land on one dashboard showing today's activity and the modules I'm authorised for. GET /api/v1/dashboard/summaryGET /api/v1/modules/entitled Role-agnostic landing page; sections render per-permission, not per-role redirect (surfaces.md §5.2).
Organisation (Enterprise Units) /organisation planned ORG_VIEW As an admin, I view/manage our branch & department structure. GET /api/v1/organisation/units ps_enterprise_units tree — org-defined types, not a fixed hierarchy.
Organisation Masters /organisation-masters planned Manage classifications As an admin, I manage department/position/responsibility masters and classification codes. GET/POST /api/v1/organisation-masters Classification catalogue admin, org-scoped.
Access Control /access-control planned Manage roles & permissions As an admin, I define roles and assign permissions to staff. GET/POST /api/v1/access-control/rolesGET /api/v1/access-control/permissions ps_roles / ps_permissions / ps_role_permissions admin.
Help /help planned Authenticated As any user, I look up in-app help. GET /api/v1/help/articles Support content, org/domain-aware.
Profile /profile planned Authenticated, own record only As any user, I view/edit my own profile and change my password voluntarily. GET/PATCH /api/v1/profile Self-service account page.

6.Healthcare — Core OPD (built, Hope Clinic)

The three entitled "Core OPD" modules plus the two CLINIC_ADMIN-only oversight surfaces (Administration, Clinic Records). All have real screenshots. Route names for the two oversight surfaces are proposed — not yet confirmed against Hope Clinic's actual network calls.

ScreenRouteStatusRoleUser storyEndpoint(s)Purpose
Front Desk — Patient Registry /modules/front-desk (tab: registry) built FRONT_DESK As front desk staff, I search for an existing patient before registering a new one, to avoid duplicates. GET /api/v1/modules/front-desk/patients?q=POST /api/v1/modules/front-desk/patients Patient intake into health_patients.
Front Desk — Today's Visits & Tokens /modules/front-desk (tab: tokens) built FRONT_DESK As front desk staff, I create a token for a patient's visit and track live queue state per doctor. POST /api/v1/modules/front-desk/visitsGET /api/v1/modules/front-desk/visits/today health_visits token/queue state machine (Waiting/In/Out/Done); surfaces prior-day visits needing closure.
Front Desk — Billing & Prescription Release /modules/front-desk (tab: billing) built FRONT_DESK As front desk staff, I release billing and the printed prescription once a patient's consult is OUT. GET /api/v1/modules/front-desk/billing/pendingPOST /api/v1/modules/front-desk/billing/{visitId}/release Closes the visit's billing/pharmacy-release state on health_visits.
Doctor Consultation — Queue /modules/doctor-consultation built MD_DOCTOR, DERMATOLOGIST As a doctor, I see my waiting/in-progress patients so I know who to call next. GET /api/v1/modules/doctor-consultation/queue Per-doctor view of health_visits, scoped to the treating doctor's own tokens.
Doctor Consultation — Consult Detail /modules/doctor-consultation/visits/:visitId built MD_DOCTOR, DERMATOLOGIST (treating doctor only) As the treating doctor, I record symptoms/diagnosis/treatment and submit a prescription, marking the patient OUT. GET /api/v1/modules/doctor-consultation/visits/{id}POST /api/v1/modules/doctor-consultation/visits/{id}/consultation Writes health_consultations + health_prescription_items; record-level check (§6.3 of 10-BACKEND.md) restricts clinical content to the treating doctor.
Patient History — list /modules/doctor-consultation/patients built MD_DOCTOR, DERMATOLOGIST As a doctor, I look up any registered patient to review their full recorded history. GET /api/v1/modules/doctor-consultation/patients?q= Directory over health_patients; every detailed-history open is audit-logged.
Patient History — visit detail modal modal, opened from list built MD_DOCTOR, DERMATOLOGIST As a doctor, I open one patient's visit list, filterable by year/month. GET /api/v1/modules/doctor-consultation/patients/{id}/visits Per-patient health_visits history; writes to ps_audit_logs on open.
Pharmacy — Prescription Queue /modules/pharmacy (tab: queue) built PHARMACY As pharmacy staff, I see prescriptions pending dispense. GET /api/v1/modules/pharmacy/queue Reads health_prescription_items where dispense is pending.
Pharmacy — Medicine Catalogue /modules/pharmacy (tab: catalogue) built PHARMACY As pharmacy staff, I maintain the shared regular-use medicine list doctors pick suggestions from. GET /api/v1/modules/pharmacy/medicinesPOST /api/v1/modules/pharmacy/medicines health_medicines — a suggestion catalogue only; editing it never rewrites an already-issued prescription.
Administration — Staff Accounts /administration (tab: staff) built route proposed CLINIC_ADMIN As the clinic admin, I create staff logins with a temporary password and see everyone's account status. GET /api/v1/administration/staffPOST /api/v1/administration/staff ps_users + role assignment for this org; sets must_change_password.
Administration — Plans & Module Pricing /administration (tab: plans) built route proposed CLINIC_ADMIN As the clinic admin, I see our current plan and available add-ons — read-only. GET /api/v1/administration/subscription Read-only view over ps_pricing_plans / ps_module_pricing / ps_organisation_subscriptions — commercial metadata, never itself an access gate.
Clinic Records — Patient List /clinic-records (tab: patients) built route proposed CLINIC_ADMIN As the clinic admin, I get operational oversight of registered patients without seeing clinical notes. GET /api/v1/clinic-records/patients Same health_patients data as Patient History, deliberately without health_consultations content.
Clinic Records — Visit History /clinic-records (tab: visits) built route proposed CLINIC_ADMIN As the clinic admin, I review the operational visit register — billing/pharmacy status, not clinical content. GET /api/v1/clinic-records/visits health_visits oversight; reuses the same VisitHistoryModal as Patient History.

7.Not yet decided

ItemWhy it's open
Frontend framework/stackNo decision recorded anywhere in streamlined/ — this file is still a stand-in placeholder. Every component/template name above is framework-agnostic on purpose.
Administration & Clinic Records — real routesReverse-engineered from screenshots only; actual Hope Clinic network calls weren't captured for these two screens the way the schema-validation pass captured others.
Where skin/theme resolution happensServer-side injected at the AppLayout/AuthLayout render, vs. client-fetched after hostname resolution — either satisfies the composition model in §1, not decided which yet.
All endpoint paths in §4–§6routes/api.php doesn't exist yet (pre-scaffolding, per project status) — these are proposed names following the /api/v1 + envelope convention in 10-BACKEND.md §8, to be confirmed when routes are actually written.