# Recruitment Desk

The hiring side of **Larkspur Health**, a community and hospital group of 4,100 people
with a hospital, a clinic, four community teams and a head office. One app for the
resourcing team that reads whole applications: the queue, the seven-step process behind
every application, the panel, the checks, the offer, and the thread where the desk
actually talks. Built on the Reckon design system.

Entry point: `RecruitmentDesk.dc.html`. Everything the product needs is in this folder;
deleting the folder deletes the product.

## Shell

Sidebar shell, labelled mode, one navigation owner, **two nav groups** (so both carry a
label):

- **Reviewing** — Your desk · Applications · Interviews
- **Running the hiring** — Requisitions · Talent pool · Insights · Desk team

Registered off the sidebar: an application, a requisition, the two create routes, your
profile, and two auth screens.

**The fold.** The sidebar folds to an icon rail on the chevron in the toolbar's leading
slot — beside the drawer's menu button, before the search field — and unfolds again from
the same button, at every width where both a rail and a labelled sidebar are
possible. **The product opens folded** — the desk's screens are wide and the nine
destinations are icons a recruiter learns in a day — and the reader unfolds to the labelled
sidebar whenever they want it, in either direction, for the session. Below 1280px the
viewport would have railed it anyway. Below 900px the sidebar becomes an off-canvas
drawer and the button is not offered, because the menu button owns the nav there and there
is no rail to fold to. The content column is the only scroll owner at every width.

## The stepper is the product

Every application moves through the same seven steps, in this order, and the stepper draws
them identically on the record, in the queue rows, on the desk and on the board cards:

`Applied · Screening · Assessment · Panel · Checks · Offer · Hired`

- A step's state — done, current, todo or stopped — is derived from the record's stage
  against `APPLICATION_PATH`. Nothing is stored per step.
- A **completed** step shows the sub-record that completed it: the screening call from the
  interviews array, the returned exercise from the documents, the last scorecard, the
  references, the offer letter. If the fixture cannot support a claim, the step says what
  it is *for* instead of asserting something.
- The **current** step shows days in it against that stage's own service level
  (`RD_POLICY.stageDays`) — three days at applied, ten at checks. "Over its service level"
  is therefore a per-stage answer, never one number for the whole desk.
- `declined` and `withdrawn` are ends, not steps: the stepper marks the step the person
  stopped at (`lastLiveStage`) and the line below names the reason and the date.

## What is derived, not stored

- **Who owes the next move.** `APPLICATION_FLAGS.awaitsUs` is the one act-ability
  predicate; the row's actions, the tab counts, the desk figures and the sidebar badge all
  call it.
- **Stalled.** Days in the current stage against that stage's service level.
- **Unread.** No `firstReadAt` — and opening the application is what sets it, so the
  figure on the desk moves when you read one.
- **The talent pool.** A predicate over the applications: settled, not hired, and marked
  as somebody we would talk to again. Reopening a person there moves their own record,
  requisition and stage — never a copy.
- **Panel averages.** The mean of the scorecards on the record. Two cards from different
  people are required before checks can start, and the primary action says so while it is
  disabled.
- **Seats.** Accepted offers against `headcount`, so three seats on one requisition behave
  like three seats. Recording the last acceptance marks the requisition filled.
- **The funnel.** Everybody who reached each step, including those who later stopped, so
  the numbers only fall.

## Commit model and reversibility

Every screen is **Instant** except the two forms (`/applications/new`,
`/requisitions/new`) and the profile, which are Staged with one Save. Reversibility:

- Harmless and instant — watching an application, reacting to a note. Bare controls.
- Consequential — every stage move, every booking, scorecards, references, notes,
  withdrawals, requisition holds. Applied immediately, undo toast, restored from a
  snapshot.
- Irreversible — declining an application and closing a requisition. Both collect the
  reason in a staged dialog first, then name the consequence line by line in a confirm
  dialog. Declined people stay reachable through the pool; a closed requisition does not
  come back.

## The chains

- **Booking the panel** writes the interview *and* moves the stage, so the ladder, the
  stepper and the badge move together.
- **Starting the checks** writes the two reference requests the policy demands, so
  "1 of 2 back" counts a real array.
- **Recording an acceptance** fills a seat, and when the accepted offers reach the
  requisition's headcount the requisition becomes filled.
- **Closing a requisition** declines every live application on it with the reason typed
  once, and keeps them all in the pool.

## Social, and why it is not decoration

An application is read by four or five people and decided by two. So the record carries a
thread: notes with `@` mentions that resolve to real colleagues, three **named** reactions
(agree, watching, question — a count of "4 agree" reads, a pile of emoji does not),
replies, a read-by count, and a follower stack derived from the owner, the watchers and
everybody who has written on it. The composer states plainly that the candidate never
sees any of it.

Faces are everywhere on purpose: candidates, panel members and the desk all carry
portraits, because a hiring queue with no faces reads like a spreadsheet of strangers.
Faces are cast, not hashed: PORTRAITS in fixtures.js names one photograph per person,
thirty-three people to thirty-three photographs, chosen against the name, the sex and
the professional age the record implies. Nobody shares a face, and a person keeps hers
on every screen because there is one entry for her. The spare portraits dress candidates
created in the running product.

## Files

| File | Holds |
|---|---|
| `enums.js` | Six status fields with tone groupings, classification lists, derived flags, transitions, policy constants |
| `nav.js` | Route table, sidebar and account projections, preview page list |
| `fixtures.js` | 9 staff, 6 requisitions, 24 applications, 10 interviews, 15 notes, 6 adverts, 10 notifications |
| `metrics.js` | Every figure in the product |
| `RdParts.jsx` | Local parts: the stepper, the thread, reactions, followers, the scorecard matrix, the stage strip, the board card, list furniture, dialogs |
| `RdShell.jsx` | Shell (including the fold), page header, view tabs |
| `DeskScreen.jsx` | Your desk |
| `ApplicationsScreens.jsx` | The queue and `/applications/new` |
| `ApplicationScreen.jsx` | One application, read properly |
| `RequisitionScreens.jsx` | Requisitions, one requisition's board, `/requisitions/new` |
| `InterviewsScreens.jsx` | The day ladder and the talent pool |
| `InsightsScreen.jsx` | The funnel, sources, offers |
| `TeamAccountScreens.jsx` | Desk team, your profile, auth |
| `App.jsx` | Routing and every commit |
| `lib/` | Contracts, render runtime, command palette, preview menu — byte-identical across products |

## Local additions (reported per AGENTS §9)

`Stepper` and `StageNote`, `Note` / `ReactionBar` / `Composer` / `NoteBody`, `Followers`,
`ScoreMatrix` and `Pips`, `StageStrip`, `AppCard`, and the day `Slot` on the interview
ladder. All are layout over existing Reckon primitives; none introduces a colour, a radius
or a type size off the scale.

## The pre-employment gate

Three things must be true before anybody starts: right to work verified, DBS clear, and —
**where the seat asks for one** — a professional registration in date and not inside its
renewal window. Which register a
seat requires is a property of the requisition (`registration: "NMC" | "HCPC" | null`), not
of the person, so `COMPLIANCE_FLAGS.registrationMissing` and `clearToStart` both take the
requisition. A ward clerk with no NMC number is not a blocked hire; reading the check off
the application alone said he was, and the compliance row now says which register the seat
asks for or that it asks for none.

## Deliberate simplifications

- **Auth is partial and says so.** Sign in and signed out are designed; Larkspur accounts
  are held in the staff intranet, so there is no reset flow in this product and therefore
  no link to one — the way out is the IT service desk line on the sign-in page.
- **No role gating.** Everybody on the desk sees the same product; salary bands and offers
  are the desk's own business. A hiring-manager view would be a second product.
- **Interview feedback is one scorecard per person per interview.** No structured
  competency framework, no calibration sessions.
- **Sponsorship is a flag, not a workflow.** One application needs it, the thread says why
  that is a problem, and the certificate ledger a real trust would keep is out of scope.
