# 21 · FRONTEND-PHASE2 — Manufacturing: Production Lines (Flows & Rules)

> **Purpose:** Frontend source of truth for Phase 2 (Manufacturing): the template canvas builder, the run board, and run execution screens. API contract and all business rules live in `30-BACKEND-PHASE2.md` — this doc never redefines them. Validation posture follows `20-FRONTEND §3`: client validation is UX only; the server is authoritative. Register this doc in `00-CORE.md §7` and `01-DOC-GUIDE.md §2`.

---

## 1. Screens

| Screen | Route (suggested) | Actual route (as built) | Purpose |
|---|---|---|---|
| Template list | `/production/templates` | `/dashboard/production/templates` — a single shared React Flow canvas, one row per template (header + stages left→right), not a list/grid | Browse templates, status, active-run count; open builder. |
| Template builder (canvas) | `/production/templates/{id}` | `/dashboard/production/templates/{id}` | Drag-and-drop stage graph design. |
| Run board | `/production/runs` | `/dashboard/production/runs` | All runs with per-stage progress at a glance. |
| Start run dialog | modal from board/list | modal from run board | Pick template, target qty, warehouse; preview scaled quantities. |
| Run detail | `/production/runs/{id}` | `/dashboard/production/runs/{id}` | Read-only graph with live statuses + stage action drawer. |

> **Build status (updated 2026-07-30).** The **backend is complete** — `Dtos/Production`, both controllers and the full `30-BACKEND-PHASE2.md` contract all exist, last verified at 312/312 smoke assertions (`Backend/smoke/run_all.py`). **Every frontend screen is now wired to it and both mock modules are deleted:**
>
> | Piece | State |
> |---|---|
> | Contract layer — `types/production.ts`, `lib/api/production-templates.ts`, `lib/api/production-runs.ts`, error-code map | **Done**, mirrors §D.1–D.4 |
> | §1 Template overview | **Real API** — live list, debounced search, status filter, paging |
> | §2 Template builder | **Real API** — loads/saves the graph with server keys, `If-Match`, persisted positions, real item/UOM pickers |
> | §3 Run board · §4 Start dialog | **Real API** — filters + paging; the dialog posts and navigates to the run |
> | §5 Run detail + stage drawer | **Real API** — canvas from the run's own positions/edges, and the full per-status action set |
> | §6 Validation posture | **Done** — `412` banner, server-driven `409 TEMPLATE_IN_USE` lock, per-action `Idempotency-Key`, silent stage-status `409` refetch |
>
> **Caveat, stated plainly: none of it has been driven in a browser.** The tree type-checks and Turbopack compiles it, but no screen has been clicked and no end-to-end run walked through the UI — blocked on AuthHex being unable to issue a token. §8 records the remaining live deviations; `Frontend/PROGRESS.md` §§11–13 is the authoritative state.

---

## 2. Template builder (canvas)

**Library:** React Flow (drag/drop nodes, edge drawing, pan/zoom, minimap). Node positions map 1:1 to `posX`/`posY`; the backend stores layout uninterpreted, so all layout behavior is client-owned.

**Node (stage card)** shows: name, role label chip, estimated minutes, input count → output count. Selecting a node opens the **stage editor panel**:
- Name, role label (free text with suggestions e.g. QA, Assembly), estimated minutes.
- **Formula rows** — Inputs: source toggle `Stock | Upstream`; Stock → Item picker (active items only) + UOM + qty/batch; Upstream → dropdown of *direct parents' outputs only* (disable others). Outputs: name + UOM + qty/batch; on the terminal stage the single output requires an Item picker (finished good).
- **Custom field builder** — add/remove fields: key (auto-slug from label), label, type (`Text|Number|Checkbox|Date|Select` + options), required toggle. Serialized to the `fieldDefs` jsonb shape verbatim.

**Edges:** drawn parent → child. Client blocks duplicate edges and self-loops at draw time.

**Client-side graph checks (UX only — server re-validates on save):**
- Cycle detection (toposort) — highlight the offending edge.
- Exactly one terminal (no-outbound) node — banner "Connect stages so the line converges to a single final stage" when ≠1.
- ≥1 entry node; no disconnected nodes (grey them out).
- Terminal output has an Item; Upstream inputs reference a current direct parent (re-check after edge deletions and clear broken references with a warning toast).

**Save:** full-graph `POST`/`PUT` with `If-Match`. Surface `422 GRAPH_*` codes by focusing the offending node/edge. **Edit lock:** when `activeRunCount > 0`, render the canvas read-only with a banner "Template locked — N run(s) in progress" (server enforces via `409 TEMPLATE_IN_USE`; the banner is UX). Deactivate action instead of delete.

---

## 3. Run board

List/grid of runs, newest first, filters: status, template, warehouse, search by doc no.

Each row/card: `docNo` (`PRD-2026-00001`), template name, target qty + finished item, created/completed timestamps, rework badge when `reworkCount > 0`, and a **stage progress strip** rendered from `stageSummary` — one segment per stage-status count using the canonical colors:

| Status | Color |
|---|---|
| Waiting | grey `#9CA3AF` |
| Ready | blue `#3B82F6` |
| InProgress | amber `#F59E0B` |
| Done | green `#22C55E` |
| Approved | teal `#14B8A6` |
| Run Cancelled | red accent on the card |
| Run Completed | full teal strip + check |

These colors are the single source for status coloring everywhere (board, run graph, drawers, legend). Show a legend on the board.

---

## 4. Start run dialog

1. Template picker (Active only), target quantity (of the finished item, unit shown), warehouse, optional output bin.
2. **Scaled preview:** client computes `scaleFactor = targetQty / terminalOutputQtyPerBatch` and shows every stage's scaled inputs/outputs *as a preview only* — the authoritative scaled figures come back on the `201` response.
3. On create → navigate to run detail. Quantity fine-tuning happens there via the per-stage quantities editor (not in this dialog).
   - **As built:** stays on the run board with a success toast instead of navigating — the new run's stages start `waiting: stageCount-1, ready: 1` and the user opens it from the board like any other run.

---

## 5. Run detail

> **AS BUILT — this section is implemented as specified, with three deviations worth naming:**
>
> - **Refetch, not poll.** The page reloads the whole run after every action rather than polling on a timer; the drawer's body is switched on the *server's* stage status, so nothing is ever rendered from a local guess. A stage-status `409` also triggers a silent refetch (§6), which covers the "someone else acted first" case a poll would have caught.
> - **No inbound-edge badges; intake lives on the node.** React Flow edge labels are cramped and a stage can have several upstream inputs from the same parent, so the aggregate `delivered/planned` badge sits on the *stage card* — colour-coded, so it doubles as an explanation of why a stage is still `Waiting`. The available-to-transfer badge is on the card too.
> - **Delivery progress is a labelled figure, not a bar.** Per-upstream-input `delivered / planned` reads better as numbers in the drawer's input list than as a row of bars, and it keeps the base-UOM vs declared-UOM distinction visible (see the consumed/returned note in `30-BACKEND-PHASE2 §D.3`).
>
> Files: `runs/[id]/page.tsx` (canvas + header), `StageDrawer.tsx` (shell + every per-status body + event timeline), `CustomFieldForm.tsx` (runtime field renderer), `RunActions.tsx` (run-level Return leftover / Cancel).

**Layout:** the template graph re-rendered read-only (same React Flow canvas, positions from the run's copied stages), each node colored by live status, with `deliveredQty/plannedQty` badges on inbound edges and an available-to-transfer badge on approved stages holding a remainder. Poll or refetch after every action.

**Stage drawer** (click a node) — content by status:
- Any status: name, role chip, estimated vs **actual** time (`actualStartAt`/`actualEndAt`, live elapsed while InProgress), event history timeline.
- **Waiting:** per-upstream-input delivery progress bars; nothing actionable except *Reject intake* when `deliveredQty > 0` (see below).
- **Ready:** *Edit quantities* (planned in/out — disabled after start, surface `409 STAGE_NOT_EDITABLE`), Stock-input availability hints (`on-hand` enquiry, advisory only — never block client-side, per `20-FRONTEND §3`), and **Start**. On start errors surface `STOCK_NEGATIVE_BLOCKED` / `ONHOLD_NOT_ISSUABLE` / `EXPIRED_BATCH_BLOCKED` with the item named.
- **InProgress:** **Complete** form — per output: produced qty, scrapped qty (reason-code picker appears and becomes required when scrap > 0), plus the **custom field form rendered from `fieldDefs`** (required fields block submit client-side; server backs with `400 REQUIRED_FIELD_MISSING`).
- **Done:** **Approve** — non-terminal: default "transfer all" with an optional per-output partial amount (validated ≤ available); terminal: confirmation summarizing the receipt (qty, computed unit cost from cost pool preview). Terminal also offers **Reject** with a strong confirm modal: *"This resets the entire run to its starting stages (rework #N). Consumed materials remain in the run."*
- **Approved (non-terminal):** *Transfer remainder* action while available > 0 (`422 TRANSFER_EXCEEDS_AVAILABLE` surfaced inline).
- **Reject intake** (on a Ready/Waiting stage with deliveries): confirm modal *"Returns work to the previous completed stage for rework"* → parents visibly flip back to InProgress on refresh.

**Run-level actions:** *Return leftover* (per started Stock input: qty ≤ consumed − returned, reason code required; hidden once run Completed — `RUN_COST_CLOSED`), *Cancel run* (reason code + note, confirm modal explaining stock return; hidden when Completed).

---

## 6. Validation posture & error surfacing

- Client checks: required/format/range, graph checks (§2), qty ≤ available style guards — all UX; never assume stock rules client-side.
- Every `ProblemDetails` renders its `title`; map domain `code`s to friendly inline messages (table in `30-BACKEND-PHASE2 §D.4`). Unknown codes fall back to the ProblemDetails title + trace id.
- `412 CONCURRENCY_CONFLICT` → "This item changed elsewhere — reloading" + refetch. Stage-action `409`s (wrong status) → refetch the run silently and re-render; another user likely acted first.
- Stage-transition posts send an `Idempotency-Key` (uuid per click) so double-clicks are replay-safe.

---

## 7. Foundation additions (PROGRESS seed)

- [x] React Flow dependency + canvas components — but **not** a single shared editable/read-only variant: the template-overview canvas (`templates/page.tsx`), the builder canvas (`templates/[id]/page.tsx`), and the run-detail canvas (`runs/[id]/page.tsx`) are three separate node-type sets (`ProductionLineNodes.tsx`, `StageNode.tsx`/`AnnotationNodes.tsx`, `RunStageNode.tsx`).
- [x] Types mirroring `Dtos/Production` (template graph, run graph, stage actions) — `types/production.ts`, rewritten against `30-BACKEND-PHASE2 §D`. The placeholder shapes are gone.
- [x] Status-color tokens (§3 table) exported from one module — `lib/production-status-colors.ts` (`STAGE_STATUS_COLOR`/`_LABEL`/`_ORDER`, `RUN_CANCELLED_COLOR`, `RUN_COMPLETED_COLOR`).
- [x] Custom-field renderer (defs jsonb → form) + builder (form → defs jsonb) — both halves: the designer in `StageEditorPanel.tsx`, and the runtime renderer in `runs/[id]/CustomFieldForm.tsx`, used by the drawer's Complete action.
- [x] Screens: template list · builder · run board · start dialog · run detail + drawer — all implemented against the real API.
- [x] Error-code → message map for §D.4 additions — all 17 codes in `lib/error-map.ts`.

---

## 8. Gaps vs. this spec

> **Closed.** Everything below was intentional scope while no backend existed; the backend is built and verified and every screen is now wired to it. Struck-through items are done; what remains is a short list of live deviations and one honest caveat.

- ~~**No persistence.**~~ Both mock modules (`lib/production-mock-templates.ts`, `lib/production-mock-runs.ts`) are **deleted**. Every screen reads and writes the real API.
- ~~**Run detail is a simplified single action, not the stage drawer (§5).**~~ The drawer (`runs/[id]/StageDrawer.tsx`) implements the full per-status action set — Start, Complete (qty + scrap + reason + custom fields), Approve with optional partial transfer, the terminal receipt confirmation, Reject for rework, Transfer remainder, Reject intake — plus the per-stage event timeline and estimated-vs-actual time with a live elapsed counter while a stage is running.
- ~~**No run-level actions.**~~ `runs/[id]/RunActions.tsx` implements Return leftover and Cancel run, both hidden once the run leaves `InProgress`.
- ~~**Stage identity on the run board/detail is reconstructed, not authoritative.**~~ `buildStagePlan()` is deleted. Run detail renders named per-stage records straight from `GET /production-runs/{id}`. **The board deliberately does not name stages at all** — its list projection carries `stageSummary` counts only, so it shows counts per status rather than guessing which stage holds which count.
- ~~**Validation/error posture (§6).**~~ Fully closed: the `412` conflict banner, the server-driven `409 TEMPLATE_IN_USE` lock banner (with a distinct message for the mid-edit TOCTOU case), per-action `Idempotency-Key`, and silent refetch on stage-status `409`s via `isStaleStageError()`.
- ~~**Save uses no `If-Match`/concurrency token** on the builder (§2).~~ It holds the ETag from the GET and sends `If-Match` on every `PUT`; the lock now comes from the server's `activeRunCount`, not a local boolean.
- **Template overview deviates from "list" (§1).** Still one shared canvas (all templates as production lines, one row each) instead of a browsable list/grid, per explicit product direction during the build. It now has a real debounced search, status filter and pagination around it.
- **`422 GRAPH_*` node focus is a substring match, not a structured reference.** The server's `detail` names the offending stages rather than returning their keys, so the builder highlights any stage whose name appears in the message. Advisory by design: the full message is always shown too, so an ambiguous name costs a highlight, never the explanation.
- **Canvas annotations are a frontend feature that grew a backend column.** The builder's grouping boxes and divider lines had nowhere to persist, which meant every save silently discarded them. They now round-trip through `PRODUCTION_TEMPLATE.annotations`; see the AS BUILT note in `30-BACKEND-PHASE2` Part C, including the client obligation to echo them back on a `PUT`.
- **Not browser-verified.** Every screen type-checks and Turbopack compiles the tree, and every endpoint behind them is smoke-verified server-side — but **no production screen has been driven in a browser and no end-to-end run has been walked through the UI.** Blocked on AuthHex being unable to issue a token (its configured MySQL host is unreachable). Tracked in `Frontend/PROGRESS.md §13`.

*End of 21-FRONTEND-PHASE2.md. Contract: `30-BACKEND-PHASE2.md`. Record work: `Frontend/PROGRESS.md`.*
