diff --git a/fusion_plating/docs/superpowers/specs/2026-05-22-shopfloor-tablet-redesign-design.md b/fusion_plating/docs/superpowers/specs/2026-05-22-shopfloor-tablet-redesign-design.md new file mode 100644 index 00000000..0ba5e11e --- /dev/null +++ b/fusion_plating/docs/superpowers/specs/2026-05-22-shopfloor-tablet-redesign-design.md @@ -0,0 +1,487 @@ +# Shop Floor Tablet Redesign — Design Spec + +**Date:** 2026-05-22 +**Status:** Brainstorm complete, awaiting user review +**Authors:** Garry Singh + Claude +**Module owners:** `fusion_plating_shopfloor`, `fusion_plating_jobs` +**Target client:** EN Technologies (Fusion Plating) + +--- + +## 1. Context + +The current Shop Floor tablet view (client action `fp_shopfloor_tablet`, OWL component `ShopfloorTablet`) was built during the initial Fusion Plating implementation. Since then the underlying models — `fp.job`, `fp.job.step`, `fp.job.workflow.state`, `fp.certificate`, `fp.thickness.reading`, `fp.job.consumption`, `fp.job.node.override`, `fp.racking.inspection` and friends — have grown substantially. Many of those new fields, actions, and workflows are not surfaced on the tablet. + +Symptoms observed on a live development instance: +- Step name shows "Active: Blasting" with no WO/customer context +- Qty rendered as "Qty 17/1" — ambiguous direction +- Step position shown as "step 1.1/11" (sequence divided by 10) +- Active timer reading **411:52:16** — a stale start that never finished and was never auto-paused +- "SIGN-OFF REQUIRED" chip is informational only; no actual sign-off control +- Customer spec, drawings, recipe overrides, milestone progress, holds, and most lifecycle actions are invisible + +Three roles operate the system: **Owner**, **Manager**, **Technician**. Technicians wear multiple hats (receiving, plating, QC, shipping) — the client explicitly wants minimal gating between roles. + +## 2. Goals + +- A technician can manage a WO end-to-end from a single full-screen workspace, without typing into search bars or jumping to the back-office. +- A manager can see at a glance: where every WO is in the workflow, what needs their decision right now, what's trending late, and where the bottlenecks are. +- All recent additions to `fp.job` / `fp.job.step` (workflow milestones, blocker reasons, recipe overrides, customer spec, etc.) are surfaced on the tablet and manager dashboard. +- Terminology matches how techs talk on the shop floor — "WO # 00001" not "WH/JOB/00001". +- The system never displays a 411-hour ghost timer. + +## 3. Non-goals (v1) + +- Multi-tablet pairing per technician +- Offline-first / PWA mode +- Voice input +- Performance optimization beyond ~500 active jobs (current scale: ~50) +- Webhooks to external dashboards +- Cost roll-up per job, cycle time per recipe, per-tech throughput (P2 — deferred to v2) +- System-wide rename of `fp.job` sequence (`WH/JOB/...` → `WO ...`) — display-only on tablet for now; back-office/reports/emails keep current sequence until a separate decision is made + +## 4. Terminology decisions + +All approved in brainstorm: + +| Element | Was | Is now | +|---|---|---| +| Page title | "Tablet Station" | **Shop Floor** (with station chip e.g. "@ EN Plating Tank") | +| Document number | `WH/JOB/00001` | **`WO # 00001`** (display only) | +| Active step header | "Active: Blasting" | **"WO # 00001 — Blasting"** (Step 1 of 11) | +| Qty | "Qty 17/1" | **"1 / 17 done"** + scrap subtext + mini progress bar | +| Step position | "step 1.1/11" | **"Step 1 of 11"** | +| Sign-off chip | "SIGN-OFF REQUIRED" | **"Finish & Sign Off"** action button (replaces plain Finish) | +| Queue heading | "My Queue" | **"Up Next"** (at this station) | +| Bath state | "OPERATIONAL / LOG: OUT_OF_SPEC" | **"Operating"** / **"Last log out of spec"** | +| Bake panel | "Bake Windows" | **"Embrittlement Bakes"** | +| Gate panel | "First-Piece Gates" | **"First-Piece Inspections"** | +| Tile set | 6 mixed tiles | **4 tech-relevant tiles**: Ready · Running · Bakes Due · Holds (others move to manager dashboard) | +| Stale timer | "411:52:16" | Auto-pause at 8h (configurable) with chatter audit; display switches to "Started Nd ago" past 24h | + +## 5. Architecture — option B (specialized components + shared services) + +Three OWL client actions, five shared OWL services, a small set of backend additions. Each client action is independently deployable. + +``` +┌────────────────────────────────┐ ┌────────────────────────────────┐ ┌────────────────────────────────┐ +│ fp_shopfloor_landing │ │ fp_job_workspace │ │ fp_manager_dashboard │ +│ (replaces fp_shopfloor_tablet │ │ NEW — full-screen WO surface │ │ refactored — 4 tabs │ +│ + folds in fp_plant_overview)│ │ │ │ │ +│ • station-scoped kanban │ │ • sticky header + WO chips │ │ • Workflow Funnel (default) │ +│ • All-Plant toggle │ │ • workflow milestone bar │ │ • Approval Inbox │ +│ • QR scan, station picker │ │ • step list + side panel │ │ • Plant Board (existing) │ +│ • tap card → JobWorkspace │ │ • sticky action rail │ │ • At-Risk │ +└────────────────────────────────┘ └────────────────────────────────┘ └────────────────────────────────┘ + │ │ │ + └──────────────────────────────────┴──────────────────────────────────┘ + │ + ┌────────────┴────────────┐ + │ Shared OWL services │ + │ WorkflowChip · GateViz │ + │ SignaturePad · KanbanCard │ + │ HoldComposer │ + └─────────────────────────┘ +``` + +### 5.1 Shared OWL services + +| Service | Used by | Props | Depends on | +|---|---|---|---| +| **WorkflowChip** | Landing card · Workspace header · Manager funnel | `{ state: {id, name, color}, nextActionLabel? }` | `fp.job.workflow.state` records (already shipped). Reads `color` field. | +| **GateViz** | Workspace step rows · Manager "Needs Worker" cards | `{ canStart, blockerKind, blockerReason, jumpTarget? }` | New `fp.job.step.blocker_kind` + `blocker_reason` computes | +| **SignaturePad** | Workspace (Finish & Sign Off) · Cert issue | `{ title, contextLabel, onSubmit(dataUri), onCancel }` | Odoo `dialog` service; HTML canvas + pointer events | +| **HoldComposer** | Workspace (Hold button) · Manager Approval Inbox | `{ jobId, stepId?, defaultQty, partRef, onCreated(hold) }` | New endpoint `/fp/workspace/hold` (with photo attachment) | +| **KanbanCard** | Landing (station + all-plant) · Manager (Plant Board + Workflow Funnel) | `{ data, density: 'compact'\|'normal', showWorkflowChip, showWorkcenter, showAssignedTo, onTap }` | Embeds `WorkflowChip` + `GateViz` badge | + +Each service is its own file under `fusion_plating_shopfloor/static/src/js/components/`. Roughly 80–200 lines OWL + 30–80 lines SCSS per service. + +### 5.2 Landing component (`fp_shopfloor_landing`) + +Replaces today's `fp_shopfloor_tablet`, folds in `fp_plant_overview`. Single entry surface for technicians. + +**Layout regions** (top-to-bottom): + +1. **Header strip** — "Shop Floor" title, station chip, station picker, mode toggle (`Station` ⟷ `All Plant`), QR scan controls (Code + Camera), refresh indicator. +2. **KPI tile row (4 tiles)** — Ready · Running · Bakes Due · Holds. Holds turns red when > 0. +3. **Kanban board** — columns = work centres; cards = `KanbanCard` (one per WO at that work centre); urgency-sorted within column (existing logic in `plant_overview.py` carries over). Drag-and-drop between columns keeps current behaviour. +4. **Optional left filter rail** (collapsed by default) — search box, priority, customer, due-by, blocker filter. Promote the existing plant_overview search bar. +5. **Footer** — auto-refresh indicator + "Last sync HH:MM:SS". + +**Mode behaviour:** + +| Mode | Columns shown | Default when | +|---|---|---| +| **Station** | Paired work centre + Unassigned + next 1–2 work centres in the recipe flow | A station is paired (via QR scan or picker) | +| **All Plant** | Every active work centre, recipe-flow order | No station paired, OR user toggles | + +Toggle persists in `localStorage` per tablet (same pattern as `fp_tablet_station_id`). + +**Card tap behaviour:** + +```js +action.doAction({ + type: 'ir.actions.client', + tag: 'fp_job_workspace', + params: { job_id: card.job_id, focus_step_id: card.current_step_id } +}); +``` + +Browser back returns to Landing with kanban scroll/mode preserved. + +**QR scan dispatch** (existing `/fp/shopfloor/scan` endpoint, unchanged): + +| Scanned | Behaviour | +|---|---| +| `FP-STATION:` | Pair tablet, switch to Station mode | +| `FP-JOB:` | Open JobWorkspace for that WO | +| `FP-STEP:` | Open JobWorkspace, focus that step | +| `FP-TANK:` / `FP-BATH:` | Chemistry quick-log dialog (existing endpoint) | +| `FP-OVEN:` | Jump to next bake awaiting that oven | + +**Auto-refresh** — every 15s. + +**Files:** + +``` +fusion_plating_shopfloor/ + controllers/landing_controller.py ← NEW (~250 lines) + static/src/js/shopfloor_landing.js ← OWL (~600 lines) + static/src/xml/shopfloor_landing.xml + static/src/scss/shopfloor_landing.scss +``` + +### 5.3 Job Workspace component (`fp_job_workspace`) + +The heart of the redesign. Full-screen surface a tech opens by tapping a kanban card. + +**Layout regions** (sticky top → scrollable middle → sticky bottom): + +| Region | Sticky | Data | Behaviour | +|---|---|---|---| +| **Back** | top | — | `doAction` back to Landing, preserves kanban scroll/mode | +| **WO header** | top | `display_wo_name`, `partner_id`, `part_catalog_id` + rev, qty/qty_done/qty_scrapped, `date_deadline`, `workflow_state_id`, `quality_hold_count`, `customer_spec_id` | `+1 Done` / `−1 Done` / `+1 Scrap` quick bumps inline. Holds count → opens Holds drawer. | +| **Workflow milestone bar** | top | All `fp.job.workflow.state` records ordered by sequence; current = `workflow_state_id`; `next_milestone_action` + `next_milestone_label` | Dots: passed (●), current (filled), pending (○). "Next" button on right fires `/fp/workspace/advance_milestone`. Disabled until preconditions met. | +| **Step list** (left/center, scrolls) | scrolls | `fp.job.step_ids` sorted by `sequence` | Each row uses step-row template (see below). Active step auto-scrolled and auto-expanded if `focus_step_id` param set. | +| **Side panel** (collapsible right) | scrolls | `customer_spec_id` (PDF), attachments, chatter | Three sub-cards: **Spec** (inline via `fusion_pdf_preview`), **Drawings**, **Notes** (chatter — read + quick-add). Collapses to icon strip on narrow screens. | +| **Action rail** | bottom | — | Always: Create Hold (`HoldComposer`), Add Note, Photo. Conditional: Issue Cert (when `_fp_has_draft_required_certs()`), Mark Done / Schedule Delivery / Mark Shipped (per `next_milestone_action`). | + +**Step row anatomy:** + +- **Collapsed** (default for done/pending/paused): one line — icon + Step N · Name + assigned tech + duration + state badge +- **Expanded active** (auto for `state == 'in_progress'`): recipe chips + instructions + primary + secondary actions +- **Expanded by tap** (any step): same shape, action buttons gated by `can_start` + +**Per-step actions:** + +| Button | Visible when | Calls | +|---|---|---| +| Start | `state in ('ready','paused')` AND `can_start` | `/fp/shopfloor/start_wo` | +| Finish (or Finish & Sign Off) | `state == 'in_progress'` | `/fp/shopfloor/stop_wo` (finish=true) OR `/fp/workspace/sign_off` if `requires_signoff` | +| Pause | `state == 'in_progress'` | `step.button_pause()` | +| Skip | `state in ('ready','paused')` AND user is supervisor+ | `step.button_skip()` | +| Move Parts | always | `FpMovePartsDialog` (existing) | +| Move Rack | when `kind == 'rack'` | `FpMoveRackDialog` (existing) | +| Quick QC | when `quick_look_prompt_ids` non-empty | `step.action_open_quick_look()` | +| Operator Inputs | when step has unrecorded inputs | `step.action_open_input_wizard()` (existing wizard) | +| Photo | always | inline camera → attach to step | +| Stop Timer (correction) | when duration looks wrong | `FpStopTimerDialog` (existing) | +| Open in backend | always (small icon) | `doAction` to fp.job.step form (escape hatch) | + +When step is **blocked** (`can_start == False`), action button row is replaced by `GateViz` block. + +When step is **opted out** (`override_ids` says excluded), row shows ✕ icon + "Skipped per recipe override" + supervisor-only "Re-include" button. + +**Auto-pause integration** — if `_cron_autopause_stale_steps` flips a step to paused, the row's chatter reflects "Auto-paused after Nh idle". Tech can tap Resume. + +**Auto-refresh** — every 15s. + +**Files:** + +``` +fusion_plating_shopfloor/ + controllers/workspace_controller.py ← NEW (~400 lines) + static/src/js/job_workspace.js ← OWL (~800 lines) + static/src/xml/job_workspace.xml + static/src/scss/job_workspace.scss + static/src/js/components/{workflow_chip,gate_viz,signature_pad,hold_composer,kanban_card}.js +``` + +### 5.4 Manager Dashboard refactor (`fp_manager_dashboard`) + +Same client action, **four sibling tabs** under a shared header + KPI strip. + +**KPI strip (extended):** keep existing 4 always-on (Unassigned Steps · In Progress · Ready to Ship · Awaiting Assignment) + existing conditional reds (Missed Bakes · Open Holds · Stale Steps · Predecessor Locked) + **2 new: Pending Cert · At-Risk**. + +**Tabs:** + +| Tab | Default? | Content | +|---|---|---| +| **Workflow Funnel** | yes | Vertical stack of `fp.job.workflow.state` records. Each row shows stage chip + count badge + first ~5 `KanbanCard`s + "+ N more" drawer. Bar chart bar behind the row scaled to count. Tap card → JobWorkspace. | +| **Approval Inbox** | no | 4 grouped strips: **Holds to Release** (`state in ('on_hold','under_review')`), **Certs to Issue** (`all_steps_terminal` + draft required cert), **Scrap to Review** (recent `qty_scrapped` bumps with operator reason), **Override Requests** (deferred — placeholder). Per-row inline action buttons + bulk-action ("Release all"). | +| **Plant Board** | no | Today's existing 3-column "Needs Worker / In Progress / Team" view — unchanged behaviour. Becomes one tab among four. | +| **At-Risk** | no | 3 sub-panels: **Trending Late** (sorted by `late_risk_ratio` desc, top 20), **Hold Reasons** (open holds grouped by `hold_reason`), **Bottleneck Heatmap** (work centres ranked by `bottleneck_score`). | + +**Cross-tab features:** live 8s refresh (existing cadence), QR scan in header, "Take Over Tablet" supervisor handover. + +**Permissions:** dashboard already gated to `group_fusion_plating_supervisor`+. That stays. Owner + Manager hit this; Technicians don't. + +**Files:** + +``` +fusion_plating_shopfloor/ + controllers/manager_controller.py ← add 3 endpoints (funnel, approval_inbox, at_risk) + static/src/js/manager_dashboard.js ← refactor: extract Plant Board, add 3 sibling tabs + static/src/xml/manager_dashboard.xml + static/src/scss/manager_dashboard.scss +``` + +## 6. Backend support + +### 6.1 HTTP endpoints + +All `type='jsonrpc'`, `auth='user'`. + +**NEW** — added by this work: + +| Endpoint | Lives in | Purpose | +|---|---|---| +| `POST /fp/landing/kanban` | `landing_controller.py` | Station OR all-plant kanban data | +| `POST /fp/workspace/load` | `workspace_controller.py` | Full Job Workspace payload | +| `POST /fp/workspace/hold` | `workspace_controller.py` | HoldComposer create (with photo) | +| `POST /fp/workspace/sign_off` | `workspace_controller.py` | Signature + finish step atomically | +| `POST /fp/workspace/advance_milestone` | `workspace_controller.py` | Fire `next_milestone_action` | +| `POST /fp/manager/funnel` | `manager_controller.py` (add) | Workflow funnel data | +| `POST /fp/manager/approval_inbox` | `manager_controller.py` (add) | Holds + draft certs + scrap to review | +| `POST /fp/manager/at_risk` | `manager_controller.py` (add) | Late-risk + hold reasons + bottlenecks | + +**KEPT** — unchanged, used by new components via wrappers: `/fp/shopfloor/scan`, `start_wo`, `stop_wo`, `start_bake`, `end_bake`, `log_chemistry`, `log_thickness_reading`, `bump_qty_done`, `bump_qty_scrapped`, `mark_gate`, `pair_station`. + +**DEPRECATED** — kept as stubs for 1 release, then removed: +- `/fp/shopfloor/tablet_overview` → calls `/fp/landing/kanban` internally +- `/fp/shopfloor/plant_overview` → calls `/fp/landing/kanban?mode=all_plant` +- `/fp/shopfloor/queue` → removed (no replacement) + +### 6.2 Model fields / computes + +On `fp.job` (`fusion_plating_jobs/models/fp_job.py`): + +| Field | Type | Purpose | +|---|---|---| +| `display_wo_name` | computed Char | "WO # 00001" formatter from `name` | +| `late_risk_ratio` | computed Float, stored | `remaining_planned_minutes / minutes_to_deadline` | +| `active_step_id` | computed Many2one→fp.job.step | Current `in_progress` step (Workspace landing focus) | + +On `fp.job.step` (`fusion_plating_jobs/models/fp_job_step.py`): + +| Field | Type | Purpose | +|---|---|---| +| `blocker_kind` | computed Selection | `predecessor` · `contract_review` · `parts_not_received` · `racking_required` · `manager_input` · `none` | +| `blocker_reason` | computed Char | Human reason (e.g. "Waiting on Step 3: Activation") | +| `blocker_jump_target_model` | computed Char | Optional tap-to-jump target model | +| `blocker_jump_target_id` | computed Integer | Optional tap-to-jump target id | + +On `fp.work.centre` (`fusion_plating/models/fp_work_centre.py`): + +| Field | Type | Purpose | +|---|---|---| +| `bottleneck_score` | computed Float, non-stored | `active_step_count × avg_wait_minutes` | +| `avg_wait_minutes` | computed Float, non-stored | Rolling 7-day avg ready→start wait | + +On `fusion.plating.process.node` (recipe node): + +| Field | Type | Purpose | +|---|---|---| +| `long_running` | Boolean | Opt out of auto-pause (24h bakes etc.) | + +### 6.3 Auto-pause cron + +```xml + + + FP Jobs: auto-pause stale in-progress steps + + code + model._cron_autopause_stale_steps() + 30 + minutes + + +``` + +Method (`fp_job_step.py`): + +```python +@api.model +def _cron_autopause_stale_steps(self): + threshold = float(self.env['ir.config_parameter'].sudo() + .get_param('fp.shopfloor.autopause_threshold_hours', 8)) + deadline = fields.Datetime.now() - timedelta(hours=threshold) + stale = self.search([ + ('state', '=', 'in_progress'), + ('date_started', '<', deadline), + ('recipe_node_id.long_running', '=', False), + ]) + for step in stale: + step.button_pause() + step.message_post(body=Markup( + "Auto-paused after %.1fh idle. " + "Resume from the tablet when work continues." + ) % threshold) + _logger.info("Auto-paused step %s after %.1fh idle", step.id, threshold) +``` + +`ir.config_parameter` key: `fp.shopfloor.autopause_threshold_hours` (default `8`). + +### 6.4 ACL changes (operator group) + +Per "techs wear multiple hats" rule — minimal new gates. + +| Model | Read | Write | Create | Unlink | Notes | +|---|---|---|---|---|---| +| `fp.certificate` | ✓ existing | **NEW ✓** | — | — | Flip draft → issued from tablet "Issue Cert" | +| `fp.thickness.reading` | **NEW ✓** | **NEW ✓** | **NEW ✓** | — | Capture Fischerscope readings from tablet | +| `fp.job.node.override` | **NEW ✓** | — | — | — | Read-only — tech sees opt-out badge | + +Supervisor-only operations enforced in `workspace_controller.py` (not via ACL): +- Step Skip (`button_skip`) +- Hold Release (state transition `on_hold` → `released`) +- Override Re-include + +### 6.5 Terminology — `display_wo_name` + +`fp.job.display_wo_name` is a computed Char that formats `name` as `WO # 00001`. All new tablet/dashboard payloads use this field. The underlying `fp.job.name` (`WH/JOB/00001`) stays unchanged — reports, emails, back-office forms continue using `name`. + +System-wide sequence rename is **out of scope** for this work. If pursued separately, it requires: (a) updating `ir.sequence` prefix to `WO `, (b) backfill script for existing records, (c) coordination with any external integrations that grep on the old prefix. + +## 7. Build & deploy sequence + +Each phase is independently deployable. Rollback is per-phase, not all-or-nothing. + +| Phase | Ships | Independently deployable? | +|---|---|---| +| **1** | Shared OWL services + JobWorkspace + workspace_controller + fp.job.step blocker_* computes + display_wo_name. Opens from existing `fp.job` form smart button. | Yes — works before Landing refactor | +| **2** | Auto-pause cron + ACL lift + `late_risk_ratio` + `active_step_id` computes | Yes — silent infra | +| **3** | landing_controller + `fp_shopfloor_landing` component. Old `fp_shopfloor_tablet` menu redirected. PlantOverview menu hidden. | Yes — Workspace already works via smart button | +| **4** | 3 new manager endpoints + manager dashboard refactor (4 tabs) + `bottleneck_score` compute | Yes | +| **5** | Cleanup: remove deprecated endpoint stubs, retire fp_plant_overview module dir | Last | + +## 8. Testing strategy + +### 8.1 Python tests (`fusion_plating_shopfloor/tests/`) + +| Test | Verifies | +|---|---| +| `test_display_wo_name` | Formatter handles various `name` shapes | +| `test_late_risk_ratio` | Correct ratio with deadline / no deadline / overdue / not started | +| `test_active_step_id` | Sole in_progress step; empty when none; first-by-sequence when multiple | +| `test_blocker_kind_and_reason` | Each kind returns correct enum + human string + jump target | +| `test_autopause_cron` | Stale flips; chatter posted; respects `long_running`; idempotent | +| `test_workspace_load_payload` | Full payload shape — keys, types, opted-out marked | +| `test_workspace_sign_off` | Signature captured, step finished, empty-sig rejected | +| `test_workspace_advance_milestone` | Fires only when preconditions met; friendly error otherwise | +| `test_hold_composer_create` | Hold + photo + qty split; rollback on validation error | +| `test_acl_operator_permissions` | Operator can issue cert, cannot skip step (controller gate) | +| `test_funnel_and_inbox` | Funnel grouping correct; inbox returns all 4 buckets | + +### 8.2 OWL tests (light) + +- `WorkflowChip` renders correct color per state +- `GateViz` renders correct copy per blocker_kind +- `SignaturePad` returns non-empty data URI after stroke +- `HoldComposer` validates qty ≤ remaining before submit +- `KanbanCard` collapses chips at compact density + +### 8.3 Manual QA checklist + +Lives at `docs/qa/shopfloor-redesign-qa.md`. 10-step walkthrough covering: pairing → Landing modes → tap card → Workspace → Finish & Sign Off → Create Hold → Manager Funnel → Approval Inbox release → auto-pause test → dark mode. + +## 9. Observability + +- `_logger.INFO` on milestone advance, hold create from tablet, sign-off, auto-pause +- `_logger.WARNING` on workspace_load with bad job_id, sign_off with empty data URI +- `_logger.EXCEPTION` on controller failures (existing pattern) +- Chatter audit on auto-pause, hold create, milestone advance, sign-off + +Future metrics (flagged, no infra now): tablet refresh frequency, time-to-Start from Workspace open, auto-pause rate, hold creation rate. + +## 10. Edge cases + +| Case | Handling | +|---|---| +| Job has zero steps | "Recipe not generated" placeholder + back-office link | +| Job has 50+ steps | Standard scroll, no virtualization in v1 | +| All steps in_progress (defensive) | `active_step_id` picks first by sequence; logs warning | +| No workflow states defined | Bar hides; Next button disabled | +| Step state changed during 15s gap | Refresh corrects; no error toast | +| Two techs tap Start simultaneously | `button_start` idempotent; second call returns state | +| Wrong station scanned | Header "Unpair" link; localStorage cleared | +| Network drop mid-action | Toast "Saving failed — tap to retry"; UI state preserved | +| 24h-bake step | `recipe_node.long_running=True` skips auto-pause | +| Customer spec PDF missing | Side panel: "No customer spec attached" | +| Funnel with 200+ jobs in stage | Top 5 cards + "View all (N)" drawer | +| Operator with no facility | Landing prompts pick-or-scan station | +| HoldComposer fails after photo upload | Photo cleaned in `except` block — no orphans | +| Manual step (no recipe_node_id) | Chips/instructions empty — OK | +| Sign-off step finished from back-office | Workspace re-renders without re-prompting | +| Tech opens Workspace for unassigned job | Allowed — read+write per "many hats" rule | + +## 11. Performance + +| Surface | Load shape | Mitigation | +|---|---|---| +| Landing kanban (All Plant, ~400 cards) | Existing `plant_overview.py` batch prefetch carries over | None new | +| Workspace load (1 job × ~11 steps) | Trivial | None | +| Manager funnel (~50 jobs × 9 stages) | Trivial | None | +| Manager At-Risk (7-day step state scan) | Potentially heavy on large plants | Cache 60s per facility | +| Auto-pause cron | Filtered query, no N+1 | None | +| `late_risk_ratio` (stored) | Recomputed on step state change | `@api.depends` triggers | + +## 12. Rollback strategy + +| Phase | Rollback | +|---|---| +| 1 (Workspace) | Hide smart-button entry in fp.job form. Workspace becomes orphan, harmless. | +| 2 (Cron + ACL) | Disable cron via UI. ACL changes are CSV-line edits. | +| 3 (Landing) | Re-enable old `fp_shopfloor_tablet` menu. Old endpoint stub still active. | +| 4 (Manager) | Revert manager_dashboard.xml to single Plant Board tab. | +| 5 (Cleanup) | Defer if issues — leave stubs longer. | + +Only stored field added is `late_risk_ratio` Float — additive `ALTER TABLE`, safe to drop. + +## 13. Backwards compatibility + +- All existing QR codes keep working — `/fp/shopfloor/scan` unchanged +- Existing `fp.job` form smart buttons → Workspace opens via doAction +- Existing `fp.job.step` form view stays as "Open in backend" escape hatch +- Old reports/emails keep showing `WH/JOB/00001` (sequence rename deferred) + +## 14. Decisions log + +| Decision | Rationale | +|---|---| +| Hybrid mental model (queue → workspace), not pure queue or pure job-first | Queue is the natural entry; full workspace solves "manage the whole job" goal | +| One tablet, no role-segmentation per persona | Client said techs wear multiple hats; minimal gating | +| Architecture B (specialized components + shared services) over mega-component | Each surface has its own lifecycle; shared services enforce consistency | +| Station-scoped kanban as default landing, All Plant as toggle | Matches physical reality (tablet at station) + provides escape hatch | +| Approval Inbox + Workflow Funnel as new manager tabs, Plant Board stays | Tactical (assignment) and strategic (where is everything) views coexist | +| Auto-pause stale timers at 8h default | Solves the 411-hour ghost timer permanently; protects cost/cycle-time math | +| WO # display only; sequence rename deferred | Lower risk; user can choose system-wide rename separately | +| ACL lift for operator group on cert / thickness reading / override read | Per "techs wear many hats" rule; supervisor-only ops enforced in controller, not ACL | + +## 15. Out of scope for v1 + +- Multi-tablet pairing per tech +- Offline-first / PWA mode +- Voice input +- Performance optimisation beyond ~500 active jobs +- Webhooks to external dashboards +- Cost roll-up per job (P2) +- Cycle time per recipe (P2) +- Notification audit feed (P2) +- Per-tech throughput (P2) +- System-wide `fp.job` sequence rename to `WO ` prefix + +--- + +**Next step:** user reviews this spec; once approved, transition to `superpowers:writing-plans` skill to produce the phased implementation plan.