# CLAUDE.md

This file guides Claude Code when working on the real HMS application in this repository.

## What this is

The **real, production-track build** of an international-standard Hospital Information
System, developed for a single hospital (multiple internal branches/campuses, not a
multi-tenant SaaS product). It is being built on Laravel 13 + PostgreSQL, module by
module, against a large pre-existing **UI/UX reference prototype**.

## The prototype — read before building any screen

`hms_ui_prototype/` is a non-functional static PHP + Bootstrap 5 mockup (no framework, no
DB, no auth, no persistence) covering ~20 hospital modules end to end. It is the
authoritative source for **screen layout, navigation, fields, and workflow sequencing** —
not code to port (it's plain PHP with hardcoded arrays). Before building any module's real
screens:

1. Read `hms_ui_prototype/CLAUDE.md` for that module's section — it documents the exact
   screen list, workflow order, tabs, and cross-module linking conventions already
   designed.
2. Read the module's requirements doc in `hms_ui_prototype/documents/*.md` (SRS-derived,
   pre-existing per module) for the underlying business rules.
3. Read the module's own design spec in `hms_ui_prototype/docs/superpowers/specs/*.md` if
   one exists — it records *why* the prototype made the UX decisions it did.
4. Match the prototype's page-per-screen structure, tab layout, and naming when building
   the real Blade views — see §Directory conventions below.

`hms_ui_prototype/documents/feedback_on_current_dev.md` is a standing gap analysis
(patient-centric architecture, RBAC data-scope, mandatory audit trail, Bangladesh
localization, FHIR-as-architecture, missing modules). Re-read it when starting phase 9
work, or any time a design decision seems to repeat a gap it already flagged.

## Development plan

Full phase-by-phase roadmap: `docs/superpowers/specs/2026-08-26-hms-development-roadmap.md`.
Modules are built in dependency order (Foundation → OPD → Billing/Pharmacy → IPD →
LIS/RIS/PACS → ER/OT/ICU → EMR/Timeline → Nursing/Doctors/Blood Bank → HR/Inventory →
differentiators), each phase getting its own design spec + implementation plan when its
turn comes. Current phase's detailed design: see the roadmap doc's "what happens next"
section for the active spec.

## Core architecture (applies to every phase)

- **Encounter-centric domain model**: `patients` is the hub; every clinical touchpoint
  (OPD visit, IPD admission, ER encounter, OT case, ICU stay) creates a row in a
  unifying `encounters` table (polymorphic to its module-specific detail table). Never
  model a new clinical module as fully independent of this spine — that reintroduces the
  "module-centric, not patient-centric" gap the feedback doc flags, and blocks the future
  cross-module Patient Timeline.
- **RBAC + data-scope, not just CRUD permissions**: every model touching patient data
  gets a Policy checking both a Spatie permission *and* a data-scope
  (own/department/branch/all, via `role_data_scopes`). Never gate a screen on "has
  permission" alone if the record could belong to a different doctor/department/branch.
- **Audit trail is core infrastructure, not an add-on**: clinically-significant models
  use the `Auditable` trait and are *amended* (with a required reason) rather than
  silently overwritten once finalized. Every new clinical model needs this from its first
  migration, not retrofitted later.
- **Single hospital, not multi-tenant**: branches/departments model internal
  organizational structure. Don't add tenant-isolation scaffolding (e.g. a `tenant_id` on
  every table) — it's explicitly out of scope.
- **Localization scaffolding is always-on**: use `__()` for every user-facing string from
  the first screen, even though only English strings exist until phase 9 — retrofitting
  this later means touching every view twice.

## Tech stack

- Laravel 13, PHP 8.3+, PostgreSQL (`hms_db_new`).
- Bootstrap 5, latest release, via npm + Vite (not the prototype's CDN pin) — reuse the
  prototype's markup/class patterns, don't invent a new visual system.
- No reactive framework (Livewire, Inertia, etc.) — controllers return Blade views, forms
  post normally. Screens needing live state (queues, boards, dashboards) use a small
  page-specific vanilla-JS file polling a JSON endpoint on the same controller, matching
  the prototype's `assets/js/app.js` style rather than adopting a new frontend paradigm.
- `spatie/laravel-permission` for roles/permissions.
- Pest for tests; TDD (`superpowers:test-driven-development`) for new work.
- Laravel Boost (Laravel-specific tooling/guidance) — install if `AGENTS.md` still shows
  the bootstrap-only stub; its output lives in `AGENTS.md`, separate from this file's
  HMS-specific architecture guidance.

## Directory conventions

- `app/Models/<Module>/` — one model per table, namespaced per module (e.g.
  `app/Models/Opd/OpdVisit.php` → `App\Models\Opd\OpdVisit`). Models that genuinely span
  every module — `User`, `Encounter` (the cross-module encounter hub — see below),
  `AuditLog` — stay flat in `app/Models/` rather than being forced into one module's
  folder just to have a folder.
- `database/factories/<Module>/` — mirrors `app/Models/<Module>/` one-to-one (Laravel
  resolves a model's factory from the model's own namespace, so the split must match:
  `App\Models\Opd\OpdVisit` → `Database\Factories\Opd\OpdVisitFactory`). Factories for
  the flat/shared models above stay flat in `database/factories/` too.
- `app/Http/Controllers/<Module>/` — one controller per workflow step, namespaced per
  module (e.g. `app/Http/Controllers/Opd/QueueController.php`); a controller backing a
  live screen also exposes a JSON action for its polling JS to hit.
- `resources/views/<module>/` — Blade views mirroring
  `hms_ui_prototype/modules/<module>/*.php` page-for-page in navigation and field
  content.
- `routes/<module>.php` — one route file per module (e.g. `routes/opd.php`,
  `routes/patients.php`), each a self-contained definition of that module's routes,
  `require`'d from `routes/web.php`. The Global Access Control module's routes live in
  `routes/rbac.php`, but keep the `admin` URL prefix, route-name prefix (`admin.*`), and
  controller namespace (`App\Http\Controllers\Admin\*`) as-is — only the route *file* is
  named for the module; renaming the URL/route-name/namespace surface was a deliberate,
  separate decision not made when this convention was adopted.
- This structure exists so a module's own code — model, factory, controller, views,
  routes — can be found and reasoned about as one unit, and so a module could eventually
  be extracted on its own. It is organizational only: it does **not** make a module
  independently deployable on its own (still one Laravel app, one database, one
  `composer.json`). True standalone deployment would mean turning a module into its own
  package (own `composer.json` + service provider) or its own application — a much
  bigger step, deliberately deferred until more modules exist and their boundaries are
  proven (this project is still in the Foundation + OPD phase of the roadmap).
- `app/Policies/` — one policy per model, enforcing permission + data-scope together
  (stays flat, not module-namespaced — policies weren't part of this convention change).
- `app/Models/Concerns/Auditable.php` — the audit trait; apply to every
  clinically-significant model.

## List screens are AJAX DataTables

Every index/list screen (Users, Branches, Departments, Designations, Roles, Patients,
Audit Log, Parent Modules, Modules, Module Links, and every new one going forward) is an
AJAX-driven DataTable, not a Blade-looped table:

- **Frontend**: DataTables.net's dependency-free v3+ build, Bootstrap 5 styled
  (`datatables.net-bs5`, imported once in `resources/css/app.css` and
  `resources/js/ajax-datatable.js`). A screen needs no page-specific JS file — mark its
  table `<table data-ajax-datatable data-url="{{ route('...data') }}">` with
  `<th data-column="...">` per column (add `data-orderable="false"` /
  `data-searchable="false"` where relevant, e.g. an actions column), and the shared
  initializer in `resources/js/ajax-datatable.js` boots it from those attributes alone.
- **Backend**: `yajra/laravel-datatables-oracle`. The controller's existing `index()`
  action still authorizes and renders the page, but now as a thin table-shell view
  (headers only, empty `<tbody>`). A sibling `data()` action on the **same controller**
  feeds the AJAX call — matching the pre-existing "a controller backing a live screen
  also exposes a JSON action for its polling JS to hit" convention — built from
  `DataTables::of($query)->make(true)`.
- **Hard invariant**: `data()`'s query must be the exact same authorized/scoped query
  `index()` already used (same `with()`, same policy/data-scope filtering) — never a bare
  `Model::query()`. An AJAX JSON endpoint is just as much a screen as the page itself for
  RBAC/data-scope purposes; this is the same class of gap the RBAC + data-scope rule
  above exists to prevent, just reachable via `fetch` instead of a page load.
- **Route**: `{prefix}.{resource}.data`, e.g. `admin.users.data`, alongside the existing
  resource route in that module's route file.
- **First column is always SL No**: every list's first column is a row serial number
  (1, 2, 3…, continuous across pages), never a data field. This is injected
  automatically by the shared `resources/js/ajax-datatable.js` initializer for every
  `data-ajax-datatable` table — a screen's Blade view never declares this column itself
  and never counts it when indexing `data-order-column`.
- **Card body keeps its normal padding** (never `card-body p-0`) — DataTables renders its
  own search box, length select, and pagination inside the same card, and those need the
  card's standard breathing room as much as the table does. A count/total column and the
  trailing actions column are right-aligned via `data-align="end"` on the `<th>` (the
  shared initializer turns this into the column's `className`).
- **Deliberate exceptions**: a screen whose job is *curating a small, hierarchical set of
  admin-managed records* rather than browsing/searching a large one — the Roles &
  Permissions matrix, Lookup Setup's group/data accordion — renders as a plain
  server-rendered Bootstrap accordion instead. Search/sort/pagination add nothing to a
  screen an admin is deliberately expanding row by row, and an AJAX-redrawn table would
  fight the accordion's own expand/collapse state on every interaction.
- **Default order**: newest-first (`order: [[0, 'desc']]` against an id/created_at
  column) for ordinary record lists. A model with an admin-curated `sort_order` field —
  today, the nav-config screens Parent Modules/Modules/Module Links — keeps `sort_order`
  ascending as its default instead, since that field *is* the admin's chosen display
  order (it drives the sidebar/nav). DataTables' own header-click sort still works either
  way; this only sets the page-load default.

## Select fields use Tom Select

Every `<select class="form-select">` in the app — a form field, a filter, a modal's
dropdown — is automatically enhanced into a searchable, styled dropdown by the shared
`resources/js/select-enhance.js` initializer. Nothing needs to opt in: mark up a plain
Bootstrap `<select class="form-select">` and it's enhanced on page load, and again on
`shown.bs.modal` for one that only exists inside a modal. It wraps the native element in
place (name/required/value/`change` events all still work), so form submission,
validation, and an `onchange="this.form.submit()"` filter select all keep working
unmodified. The one exclusion is a `<select>` inside `.dt-container` — an AJAX
DataTable's own generated "entries per page" control, not a real form field.

Tom Select, not Select2: Select2 is a jQuery plugin, and this app's AdminLTE stack is
deliberately jQuery-free (see Tech stack below) — Tom Select is the actively-maintained,
dependency-free equivalent with an official Bootstrap 5 theme
(`tom-select.bootstrap5.css`), so it was used in Select2's place without adding jQuery as
a dependency.

## Conventions carried over from the prototype

- Status/severity is never color-alone — always paired with an icon or text label (WCAG
  2.1 AA, binding).
- Destructive/financial/status-changing actions use a confirmation modal.
- A module needing context from another module's record (a visit, order, admission) uses
  a `?from_<module>=` query param + a small context strip — the prototype's established
  cross-module convention; match it rather than inventing a new mechanism.
- A module's own masters/setup screens are separate from its operational screens (e.g.
  `opd-setup/` vs `opd/` in the prototype) — keep the same split in the real app's admin
  areas.
