# 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.