> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hr-easy.nlead.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Project structure

> Where code lives, the module contract, and the rules about crossing boundaries.

## Layout

```
src/
  app/
    (auth)/          # sign-in
    (dashboard)/     # HR & management surface
    (portal)/portal/ # employee & applicant self-service
    api/v1/          # versioned API routes
  modules/           # feature modules
  components/
    ui/              # shadcn/ui primitives
    ui-patterns/     # shared page chrome
  lib/               # shared layer
  messages/          # de.json, fr.json, en.json
prisma/
  schema.prisma      # single source of truth
  migrations/
openapi/             # generated portal contract
docs/                # documentation, including this site
scripts/             # CI guards and generators
e2e/                 # Playwright
```

## Modules

Every new feature goes in `src/modules/{module}/`:

```
services/     # business logic — the brain
components/   # React components — the face
types.ts      # module-specific types
index.ts      # the public API contract
```

`index.ts` is a **curated contract**, not a barrel export. Only what other modules
legitimately need is exported; everything else is internal. Cross-module code imports
from the contract, never from another module's `services/` directly.

<Warning>
  "No barrel exports" means no `index.ts` that mechanically re-exports a whole directory. A
  hand-picked public API is the opposite of that and is the required pattern.
</Warning>

## The shared layer

`src/lib/` holds what genuinely crosses modules:

| Path                       | Contents                                                   |
| -------------------------- | ---------------------------------------------------------- |
| `lib/auth/`                | Session management, roles, permissions, access helpers.    |
| `lib/db/`                  | The Prisma client singleton.                               |
| `lib/audit/`               | Audit logging, masking, IP truncation, the event bridge.   |
| `lib/storage/`             | Blob storage.                                              |
| `lib/email/`, `lib/graph/` | Mail and Microsoft Graph.                                  |
| `lib/crypto/`              | Application-layer field encryption, Key Vault bootstrap.   |
| `lib/contracts/`           | Contract templates, variables, and the portal API schemas. |
| `lib/swiss/`               | Cantons, AHV numbers, address and phone normalisation.     |
| `lib/observability/`       | The client-safe logger and the App Insights sink.          |
| `lib/events/`              | The in-process event bus.                                  |
| `lib/pipeline/`            | Shared specs for the CI automation, with tests next door.  |

## Route conventions

| Kind      | Location                            |
| --------- | ----------------------------------- |
| API       | `src/app/api/v1/{module}/`          |
| Dashboard | `src/app/(dashboard)/{module}/`     |
| Portal    | `src/app/(portal)/portal/{module}/` |

## Boundary rules

<AccordionGroup>
  <Accordion title="No database access outside services or src/lib/">
    A route handler that calls `prisma` directly has put business logic in the transport layer.
    Route handlers orchestrate; services decide.
  </Accordion>

  <Accordion title="Server-only code stays server-only">
    Modules with AI, XLSX or e-mail services keep them behind the contract and never import them
    into client components. The observability logger in `lib/` is intentionally client-safe and does
    **not** import the App Insights sink; the module-level logger does, and is server-only.
  </Accordion>

  <Accordion title="No environment access in business logic">
    Read `process.env` at a module boundary and pass values in as parameters. A service that reads
    its own configuration cannot be tested without the environment.
  </Accordion>

  <Accordion title="Zod parses at the boundary">
    Parse external input — HTTP bodies, database JSON, third-party API responses — at the entry
    point, then pass typed values inward.
  </Accordion>
</AccordionGroup>

## Size limits

Component files are limited to 200 lines by convention, warned past 200 by ESLint, and
**failed past 800** unless the file is grandfathered in a baseline. Grandfathered files
may shrink but never grow — the ratchet only turns one way.

## Events

Modules communicate through the event bus rather than by importing each other:

```typescript theme={null}
eventBus.emit("employee.onboarded", { employeeId });
eventBus.on("employee.onboarded", async (data) => {
  /* … */
});
```

Names are `{module}.{entity}.{action}`. Handler registration happens through
side-effect imports in the instrumentation entry point, not through the module
contract — see the pre-boarding orchestrator for the reference implementation.
