design
Production Manager — design notes
Section titled “Production Manager — design notes”Persona surface for the Supply Chain & Parts, Vehicle Operations and Production — Product Specification. Web flow, canonical Atlas shell (1440). System access tier is labelled “Forge Manager”; the real title is Production Manager (e.g. Tobi Oyeshile, Sango facility). “Sango facility” is this production site; the central inventory warehouse it draws from is a separate entity, “Sango warehouse”.
Persona
Section titled “Persona”The Production Manager requests the materials production needs and runs work orders and assembly at a facility. The defining pain: a request for 10 rolls of tape came back as 4 with no explanation, because Retrofit drew on the same stock — cross-team demand was invisible and the split had no recorded reason.
Problems being solved
Section titled “Problems being solved”- No end-to-end visibility on a request once submitted; status has to be chased.
- When a request is delayed or partially fulfilled, the reasoning isn’t shown.
- Cross-team demand for the same item isn’t surfaced at request time.
- No consumption logging at point of use, so reorder points are guesswork and unused stock has no route back. Now represented: step draws and usage logged on the floor take stock out of the shared pool (each a movement on the Ledger) and feed the BOM have/need floor signal (“Short for active WOs”) on My Inventory.
Navigation
Section titled “Navigation”Sidebar (per PRD §3): the same items, now grouped under .sb-group section headers — the facility name (Sango facility) as context, then Overview (Dashboard), Inventory (My Inventory), Production floor (Work Orders, Assembly Procedures), Facility (My Facility, Category approvals). No nav items added or rewired; only regrouped. Dashboard is the landing view. Breadcrumbs on every view. Compact create / confirm / edit records open in a centred .dlg; rich records (a work order, an assembly procedure) open as full inner detail pages (.pg-back back to their list, parent nav item stays highlighted) — never a side drawer. Filters live in each .tbl-card-head; dates on every row.
| Slug | What it does |
|---|---|
dashboard |
Production-floor health. Eight floor signals as .pstat cards across two rows of four — row 1 output & delivery (Active WOs of total · Units closed today · On-time completion · Behind schedule of active), row 2 quality & supply (First-pass yield · Rework rate · Open defects · Material fill rate). First-pass yield replaces the old QA-pass-rate card (a stricter quality signal: units passing QA with zero rework loops); the old Ready for pickup card is dropped since My Inventory already carries an Awaiting pickup KPI (#kpi-pickup) and the header has an Accept receipt action. Each KPI carries a .trend delta on the value’s own line (.trend-up/-down/-flat/-neutral with an SVG chevron — a directionally-neutral metric like more WOs reads neutral, not red; a worsening-when-up metric (Behind schedule, Open defects) that rises reads red (.trend-down) with an up chevron matching the delta sign, never green; green .trend-up is reserved for genuinely-good movement — including a good-when-down metric like Rework rate falling, shown green with a down chevron). Below the KPIs a .chart-row gives direction + one decomposition: a 7-day units-closed mini column chart (.spark) beside an Active WOs by stage .cbar breakdown (In progress + Open sum to the Active-WOs KPI — the decomposition sits below its parent, never beside it). The old materials-readiness table is removed (it duplicated the readiness signal My Inventory already carries). Dates throughout. Quick-links route into Work Orders / My Inventory. |
inventory |
Mirrors the Forge app’s shared pool. A .pseg strip swaps Stock / Ledger / Requests / Outgoing / Incoming / Item requests. The page head carries two actions, Return to warehouse and Request items. A My-Inventory summary row of four .pstat KPIs sits above the tabs: Items in pool (#kpi-skus, distinct lines), Low stock (#kpi-low, lines at or below their low threshold, from refreshHealth; opens Stock filtered to Low), Short for active WOs (#kpi-short, BOM lines the pool cannot cover) and Incoming (#kpi-pickup, fulfilled requests to receive; opens Incoming). Stock (#inv-stock-tbl) — the pool’s lines: Item (28px .it-thumb beside the name, category beneath) · Tracking (Serialised / Consumable, a serialised line’s first serial + +N beneath) · On hand · Usable · Incoming (each with its unit) · Health (Healthy / Low / Critical against the line’s threshold: Low at or below it, Critical under 60% of it, refreshHealth) · Last movement (the line’s newest Ledger movement, with its date and who) · Request. Search, one filter (All items / Low stock / Serialised / Consumables) and Export CSV. Row-click opens the item detail, whose primary action is Request item (requestItem). Units are the canonical lowercase-singular set pcs · each · roll · metre · litre. Ledger (#inv-ledger, #lg-tbl) — the pool’s append-only movement history, newest first; a row opens the item it moved (openLedgerItem): When · Movement · Item (serials beneath, first + +N) · Qty (signed) · On hand after · By · Reference (request, work order and unit, step, return reference, reason; usage logged with no job reads No work order). Movements are Received, Used on unit (drawn for a step), Used (logged on the floor, Forge FR-FS11 to FR-FS13), Returned to warehouse, Count adjusted and Reversed. Filters: search, Movement, Item, By, Work order (with No work order) and a When date range; Export CSV carries the filters. On hand after is walked back from each line’s current on-hand (prepLedger), so the Ledger and Stock always agree. The item detail shows lot / expiry for consumables (LOT, #it-lot-row) with an In date / Expiring soon / Expired badge (lotStatus vs the flow’s reference date); shelf-life items (adhesives, oils) carry an expiry, hardware consumables are lot-only. Request column: the last column is a per-item Request action (refreshReorder renders a .rq-cell with a .link-add per row) that opens Browse all items focused on that item with its quantity preset to 1 — there is no reorder-badge column any more. (A per-SKU reorder point is still tracked internally for the item detail, but no longer surfaces as a dashboard KPI — the dashboard’s sixth card is now Ready for pickup.) A Unit column carries the weight/length dimension; continuous consumables (Loctite 243 in litre, Differential oil in litre, Power cable 6mm in metre) deplete in their measured unit so a partial pour never falsely reads 0, and the item detail flags them as continuous. Requests — one status-tracking list of every request this facility has raised, the manager’s own and the team’s alike: .pstat KPIs (With the inventory manager, Open, Accepted, Rejected) with Pending — a subset of Open — as a .chip beneath the row, status/date/search filters, a table (request + WO link · requested by · items · status badge · requested · updated · view) and a row-click detail carrying requested-by, reason / purpose, date required, approved-by, the requested lines (requested qty in unit · available · fulfilled), the linked WO or General request, a status timeline, and the split or rejection reasoning. The approval gate is not here. A team member’s request goes to the pool’s Production Inventory Manager (Ifeoma Chukwu), who approves it onward to the warehouse or rejects it with a reason; this console reads the outcome and never decides it. Such a request sits at With inventory (badge b-amber) until the gate moves it, and the detail’s timeline names the inventory manager as the pending approver. A request the manager raises directly is unchanged and starts at Requested. The Request items dialog (#dlg-request) is WO-BOM-driven (the Forge model). It opens on two sections, no single-item line picker: From your work orders — one .rq-wo-box card per assigned work order that carries a BOM (REQ_WOS × perUnitBom), each showing the WO reference + assembly and a have / need .line-tbl where have is current facility on-hand (onHandOf) and need is the per-unit BOM quantity × the work order’s unit count (so a WO for N units needs N × each line). A fully-covered WO shows a green “You already have all the BOM items” state (no action); a short WO shows an “Add N missing item(s) to request” primary button that raises a request for exactly the shortfall (need − have) of the short lines and submits them (requestWOShortfall) as one single-item request per shortfall line (each raiseRequest with a single line, all linked to the WO) so they land as separate rows on the request table and are fulfillable per item on Inventory, then flips the card to Already requested in place. Per-WO dedup (_woRequested, seeded from REQ and added to on submit): once an open request is linked to a WO its card reads Already requested and the add action is disabled — unless the prior linked request was Rejected or Partially fulfilled, which does not block (so the partially-fulfilled WO-9007 stays requestable while the pending-linked WO-9005 does not). Other items — a Browse all items button opens the ad-hoc catalog (#dlg-catalog): a searchable full-catalog .line-tbl with a .cat-qty per row where the user sets quantities, plus a required Reason / purpose and required Date required (submit blocked with inline errors until both are filled) and urgency; this is the only free-form path (submitCatalog → raiseRequest). Outgoing — returns sent back to the warehouse (sent to · destination · items · status · date created · view), its status/destination/created-date filters in one Filters .dlg with an active-count badge, search inline. Return to warehouse (#dlg-push) returns only what the pool actually holds: the item picker lists on-hand lines and addPushLine caps the quantity at on-hand. Each return captures a Condition (Unused · Refurbished · Used · Faulty), a Faulty return captures the serial of each faulty serialised unit (renderFaultySerials), and a Note is required (#ps-note-err). Returns land in Inventory’s returns lane, where deciding quarantine is the Inventory Manager’s job. Incoming — deliveries from the Central warehouse (never Procurement direct), each Ready for pickup until accepted (set when Inventory fulfils the request and raises the delivery note); the whole row is clickable to an items-received detail. Accept draws the delivered items into My Inventory on-hand (adjustStock → refreshReorder), flips the row to Delivered, turns the row action into a clickable View (opens the items-received detail — never a dead “Delivered” label), and toasts “Items received into my inventory”. Accept captures a received quantity per item vs what was requested (#ac-qty table: Item · Requested · Received input, defaulting to the delivered qty) — the received amount is what populates on-hand. Serialized items require a serial per received unit (renderAcceptSerials regenerates one input per received unit as the qty changes; accept is blocked until all are keyed) so inbound genealogy is recorded. The receipt then reconciles against the linked original request (fulfilRequestOnReceipt reads the Fulfils REQ-#### ref off the row): a full receipt closes it Accepted, a short receipt marks it Partially fulfilled (each line’s fulfilled = what was received, with the shortfall reason recorded) — status, lines, timeline, and the Requests table row all update. The receipt also captures lot + expiry for each consumable (renderAcceptLots → LOT, persisted onto the item). Accepted deliveries leave the incoming queue (the queue only holds what’s still awaiting pickup) — the record now lives on the linked request and in stock. A Partially fulfilled request detail shows the outstanding balance per line and a Request the balance action (requestBalance) that raises a follow-up request (backorder) for the shortfall. The accept dialog is a clean .ac-meta header + .ac-qty-tbl (Requested vs Received, short receipts flagged) + serial/lot capture sections. Faulty/damaged units are not flagged at accept — everything is accepted, then pushed back to Inventory afterward via the Condition column. Folds the old request/receipt model in. A fifth .pseg sub-panel, Item requests (#inv-itemreqs, #itemreq-tbl), holds requests for items the catalog does not carry: Request · Item · Category · Unit · Qty · Location · Requested · Status, with search plus status and location .filt-selects (filterItemReq). Raising or reading one is a page, not a dialog — #v-newitem (openNewItemReq), an inner page whose .pg-back returns to this tab, laid out as .nit-grid: the form on the left, the approval line on the right. That line is the flow’s own .tl-item timeline — Requested (the manager and the date) → Line manager approval (Head of Production) → Procurement (the request reference once raised) → Inventory issues it — each step done, pending, or warn where the request was rejected. The item name is composed, never free-typed: the nir- prefixed name composer ported from the Inventory Manager gives an Item type combobox over the controlled ITEM_TYPES list (searchable by alias), plus Describe it, Key specs and an optional Version + How it differs, forming the name live as [Describe] [Type] [Version] (Key specs, How it differs). Its duplicate guardrail checks the composed name against this facility’s own stock (_catalogNames reads #inv-stock-tbl): an exact match turns the preview red and blocks Send for approval, offering Open existing (which jumps to the stock tab filtered to that item); a similar name warns amber but allows it. The rest of the form is Quantity needed · Category · Unit · Why it is needed · Industry standard or spec · Location · Need by · Urgency · Photo, with .f-err inline validation, and the requester and date are captured automatically. Sending appends the request as Pending approval and returns to the tab. Opening an existing record renders the same page read-only with no submit action. |
facility |
A .pseg strip swaps Overview / Team. Overview — Sango facility summary card (address, status, state, LGA, date created) beside a team-composition card. Team — full Sango facility roster (name + email + role + status badge), role/status filters, search, and an Add team member dialog that appends a row. |
workorders |
Forge-floor work orders. Five .pstat KPIs (active WOs, units closed today, behind schedule, open defects, QA pass) reconciled to the dashboard’s production-floor numbers so the two surfaces agree — 18 active of 30 · 14 units closed today · 3 behind schedule (= the past-pace rows) · 2 open defects · 96% QA pass; the old hardcoded “+13248h” slip is gone. Table — each row leads with the unique WO reference (WO-90xx, in .sn tabular figures) as the primary line so five distinct WOs all at “Step 2” are individually scannable, with the “N × output item” as the sub. Columns: work order (ref) · status · progress bar · declined · team avatars · Due date · Started — the assignee column is labelled Team and shows the number of people assigned to the work order (“N assigned”, “0 assigned” when none — never a bare “No team”); on the WO detail the Team meta counts the distinct people across the WO’s steps; every row shows a Due date and started WOs a Started time (open WOs read “—” / “Not started”). Row-click opens the Work order detail inner page (#v-wo-detail, slug wo-detail, .pg-back to Work orders). Persona-scoped actions: New work order (a WO can be created on either Atlas or Forge and populates both systems — Forge owns all the downstream execution actions; retrofit WOs are created from the Retrofit flow, not here), Plan day, Export. Status, team and start-date filters (3) collapse into a single Filters .dlg (Reset / Apply, active-count badge); search stays inline. Archived work orders drop out of this list (filterWO always hides data-status="archived"). |
wo-detail |
Rich work-order record page. Header — canonical detail-page head (a .pg-back text button back to Work orders → .pg-head › .pg-title-row holding the title “N × output item” with the status .badge (Open / In progress / Closed / Archived) inline, a .pg-sub metaline (facility · pace) beneath, and .pg-actions for the buttons only): a Park WO ↔ Set live toggle, an Actions ... menu (.menu-pop: Manage assignees · Export · Close all remaining units), and an Archive button shown only while the WO has not started. Reorganised for a distinctly-DS-canon read (never .seg-prog): the head is followed by a .pstat-row of four key WO stats (Closed units of N · In progress (on the line + awaiting QA) · Defects in rework · QA pass %, each #wo-k-*, filled per WO and clickable into the Units pane via woKpiTo), then the .pseg (data-canon="allow-pseg-filter" not needed — it sits above the panels, not in a table) swaps Overview / Units, then the sections. Overview — a batch progress treatment that is a single linear track in brand green (.wo-prog-track > brand-fill span, muted --grey2 remainder — the canonical .prog-bar shape, never the app’s multi-segment blue bar) reading “X / N closed · P%”, with the per-state unit counts as small .badge legend chips beneath (.wo-legend: Closed·QA pass b-green / Awaiting QA b-amber / In progress b-blue / Rework b-red / Queued b-grey); a .kv-grid meta strip (Family, Type, Started time, Elapsed (a live run-timer counting from when the first unit started — #wo-elapsed/woElapsedMin, ticking while the WO runs), Due date, Team, SOP steps, QA pass %, Avg cycle); Park WO ↔ Set live actually pauses/resumes the WO — parking banks the elapsed minutes and freezes the clock (shown “· paused”), Set live resumes it (togglePark); parking is a shared Atlas + Forge action — the same WO can be parked from either surface and a park on Atlas pauses the line on Forge (and vice versa); the procedure shape as full-width .shape-cards that mirror the assembly procedure tied to this WO — step names, checklist, BOM, minimum-people and SOP image all come from the matched procedure’s meta (woShapeFor pulls meta[i].checklist/meta[i].bom etc.), and per-step assignees seed from the procedure too (ensureWOAssignees reads the procedure’s meta[i].assignees, falling back to WO crew), so managing assemblers or editing the checklist/BOM on the procedure changes what the WO run shows. Editing procedure assignees applies to already-open WOs — saveAssignees (proc mode) clears the cached stepAssignees on every WO that uses the procedure so they re-seed from it on next render. Each card carries the step name, a BUILD/QA badge b-blue/b-purple, its assignees, the BOM as .shape-bom pills, and the Checklist as a stacked, wrapping .shape-checks list (full-width lines, not inline pills, so long step explanations read cleanly) — each shape step also echoes the procedure’s SOP reference image (.shape-sop, visual aid); a Manage assignees link (#dlg-assignees) sits at the top of the step list, and each step reads “N / min assigned” with an amber shortfall signal (.shape-short, “needs X more”) when it is under the step’s minimum. The minimum is a hard gate on the run, not just advisory: while any step is understaffed (woStaffingGap), the Overview shows an amber “N steps understaffed” banner and completing the batch is blocked (Close-all-remaining toasts “assign the minimum people first”). Per-step QA photo capture is intentionally not here — capturing a photo against the actual step being worked lives on the Forge app (floor execution), not this back-office view; and an Activity feed (.tl, ~10 seeded entries, prepended on each unit close). Units — a .tbl of per-unit rows (WO-ref · Unit N as the row title so two work orders sitting on the same step are never confused · serial · BMS ID (battery builds only — any item with “rept”/“battery” in the name, isBatteryOutput; column toggled by .show-bms, the BMS input on the last + QA step on Forge) · status · current step + step-progress bar · Step assignee · QA · time). Assignees live on the steps, so a unit has different assemblers across its steps: the Step assignee column shows whoever is on the unit’s current step (unitStepAssignees), displayed compactly as one name with a “+N” chip (fmtAsn) rather than a comma list, and the state/assignee filters filter on that; the assignee dropdown lists the union of everyone assigned across the WO’s steps. The Assembly record dialog carries a Step assignees breakdown listing every step with its assignee(s) and marking which step the unit is On / Done / Queued) with a state filter (#unit-seg, a .filt-select dropdown — six states in a table head is a dropdown’s job, driven by unitFilter), an assembler .filt-select, and a serial .filt-search; a row opens the Assembly record .dlg (#dlg-record: the serial, a per-step Step assignees breakdown, and QA gates passed/failed — read-only, since QA and close happen on Forge; the old flat “Components scanned” list is gone, since BOM now lives per-step on the procedure). Serial + BMS come from Forge — step completion, QA and the serial are all recorded on the Forge floor app, so the web unit record is a read-only monitor (no per-unit QA/close action, no serial confirmation, with a Forge note). Every output is serialized and the serial is auto-generated by the system when the unit passes QA (MOC-X-SAN-<yymmdd>-<seq>, shown on #rc-serial once passed, “Issued when the unit passes QA” before). Battery Pack outputs additionally carry a BMS ID (input on the last step + QA step on Forge): the Units table shows a BMS ID column for battery-pack WOs (isBatteryOutput, toggled by .show-bms on #unit-tbl) and the record shows the BMS detail. A completed WO still mints its output into Inventory — confirmCloseWO (Actions › Close all remaining) flips every open unit to Closed, increments the output item on-hand, and — because this is a manager force-close that bypasses the normal Forge QA-pass serial issuance — asks the manager whether to generate serials (#cw-gen-serial checkbox, off by default): checked mints each serial, unchecked closes the units without serials so they can be issued later if they clear QA. The Actions-menu Close all remaining units closes every open unit at once (confirmCloseWO), mints the batch into Inventory, and sets the WO Closed (components are not consumed here — they were committed when the WO was created: confirmNewWO → consumeWOCreation draws the per-unit BOM × qty from on-hand at creation, the reserve/allocate-at-release model, so stock reflects the commitment up front. The per-unit BOM is the single source of truth: perUnitBom(kit) aggregates the tied procedure’s per-step BOM (sum of every step’s lines), falling back to the static BOM_PER_UNIT only for a kit with no authored procedure BOM — so both the request have/need and the WO commitment derive from what’s authored on the procedure steps. (Editing a procedure’s steps preserves each surviving step’s authored BOM/checklist/assignees/min/SOP by name+kind, so the step list can change without wiping per-step work.) rather than at close) — the whole-WO close reflecting the same per-unit model. Archive (openArchive) is offered only when no unit has started (woStarted false); it opens #dlg-archive with a required reason and, on confirm, sets the WO Archived (b-grey) and drops it from the active list. Once any unit is open/in-progress the Archive action is hidden. |
procedures |
The versioned assembly playbook per item. Table (item + item type as muted plain text, not a badge · version · status · assembly type · steps · active WOs · last edited). Status / item-type / last-edited filters. Row-click opens the Assembly procedure detail inner page (#v-proc-detail, slug proc-detail). The New procedure .dlg (#dlg-newproc, shared with Edit) uses a searchable item picker (<input list> over the full catalog), a version field, and an Assembly type chooser of two selectable option tiles (.pick-tile) — Bench (“One technician walks each unit through all steps”) and Line (“Each step is its own workstation; units flow across them; requires per-step assignees”) — plus build-steps and QA-steps textareas (one step per line, shown as a persistent .f-hint not just placeholder text). A step’s minimum people is set afterward with the Min. people dropdown on its step card (see proc-detail), not via typed syntax. New procedure drafts a Draft row; Edit procedure opens prefilled and Save updates the record + table row and resets step meta. |
proc-detail |
Rich procedure record page. Header — canonical detail-page head: a .pg-back text button back to Assembly procedures → .pg-head › .pg-title-row (item + version, with the status .badge (Draft / Active / Deprecated) and assembly-type .badge (Line / Bench) inline) over a .pg-sub metaline (family · version · editor); .pg-actions holds an Actions ... menu (Edit steps · Duplicate to draft · Deprecate) and an Activate this version button. Step cards — a vertical list of full-width .step-cards (.step-row is a column, not a 216px horizontal scroller — real SOP step text is too long for narrow cards). Each step is fully authored on its card: name · BUILD/QA badge · “N / min assigned · N BOM · N checklist” counts · the Bill of materials as .shape-bom pills · the Checklist (the step’s acceptance items, st.checklist — an array authored here, not a generic pool) as a stacked wrapping .shape-checks list · the Min. people select · the SOP reference image slot (add or replace — sopSlot now renders a working file input in every state, .sc-sop-replace). A .sc-edit footer gives per-step Assignees (openAssignees('proc')), Edit BOM (#dlg-step-bom — a catalog item+qty picker writing meta[i].bom) and Edit checklist (#dlg-step-checklist — one-item-per-line, writing meta[i].checklist). Everything a step carries — assignees, BOM, checklist, min, SOP — lives on the procedure meta and flows to every WO that uses it, each carrying a Min. people select (.sc-min-sel, a 1–6 dropdown · floor of 1 · setStepMinTo — a dropdown rather than typed syntax so authoring is unambiguous) that sets how many people the step needs, with a “needs X more” / “met” signal, and each also carrying an SOP reference image slot (.sc-sop, visual aid): the authored image (or a placeholder when set), or an Add reference image affordance that uploads from file — NOT camera-only, since the SOP image is authored ahead of time (onSopImage, sopSlot). The same image echoes into the WO procedure-shape step so the run reads visually. Ends in a .step-add “+” to insert a step. Activation readiness — a checklist panel (procReady): “At least one step”; per step ✓/✗ “fixAssign); “All QA steps have a checklist item”. The Activate button is disabled until every check passes (activateProc flips Draft → Active and updates the list row). |
cat-approvals |
Dual sign-off with the Retrofit Station Manager on new inventory categories; reject requires a reason — the reject .dlg uses the canonical .fld block with a .fld .req span as the required marker (the old label.req pattern rendered grey, so it was replaced) (no bespoke .f-group/.inp). |
Deep-linkable via ?scenario=<slug>.
Send feedback
Section titled “Send feedback”Every persona console carries the same Send feedback action: a topbar .icon-action (message icon) opens #dlg-feedback — Type (Bug / Improvement) · Impact (Blocking / Slows me down / Minor) · Where (this persona’s own pages, preselected to the page it was raised from) · Details · optional Screenshot. sendFeedback() validates the required fields and confirms with a toast. Submissions land on the Product Manager · Tricycle Hardware console’s Tech feedback queue, which triages them by type, impact and area.
Requirement coverage
Section titled “Requirement coverage”FR-11 (cross-team reasoning visible on dashboard), FR-12 (dashboard), FR-13/FR-25 (accept receipt = consumption feeding reorder), FR-14 (transfers in/out), FR-15 (urgency on requests), FR-20 (auto team tag), FR-30 (request note), FR-35 (filters + dates). Conventions: NFR-6/7/8/9.
Cross-links
Section titled “Cross-links”Links to the Procurement Manager flow for an end-to-end demo.
Candidate future personas
Section titled “Candidate future personas”Per PRD §11/FR-29 — a Line Manager who can see every request submitted by their team is the natural role above this surface.
Canon sweep (2026-08-03)
Section titled “Canon sweep (2026-08-03)”Swept to the Atlas shell and canon atoms. What changed structurally:
- Topbar — the fixed “Atlas / Dashboard” prefix became the canonical two-part crumb
(
#crumb-parent/#crumb-parent-sep/#crumb-current) driven bysetCrumb()fromgoto(); added the.loc-pill(“Forge · Sango”); the bare.user-pillbecame the full.acct-wrapwith Switch role (#role-dlg), Settings and Logout. The loose sign-out icon is gone. - Notifications and Settings did not exist. The bell fired a toast and went nowhere,
and the account menu called an
openSettings()that was undefined. Notifications is now the canonical.ntf-rowlist grouped under.ntf-secday headings, built from the events this flow already raises; Settings is the two-pane.settings-layoutpage. Both are inner pages with a.pg-backto the Dashboard. - Tab strips kept as
.pseg, deliberately.#inv-segand#fac-segswap whole sections and#req-segcarriesdata-canon="allow-pseg-filter", so.psegis correct for all three. Only#ca-segfiltered a table, and it became a.filt-select. - Filters — both floating
.filt-bars folded into the.tbl-card-head. This flow’s.filt-selwas replaced by.filt-selectand its.filt-rangeby.filt-date; three heads carrying three or more controls collapsed behind one Filter button and the shared#dlg-filtersmodal with a counting badge. - Card heads carry controls, not prose. Head order is title · spacer · search ·
filters · actions, so with a title present the search sits on the right. Four prose labels were removed (never
.card-metain a head), and no head carries a CTA any more: “View all requests” was a duplicate of a page action, and “Add team member” moved into a new.pg-actionson the Facility page, shown with the Team tab it belongs to. - Guidance panels are dismissible. All nine
.panel-blue/-amber/-greenpanels gained a.panel-xclose control;dismissPanel()removes the panel so the gap closes with it. - Field markers — the required asterisk was styled by a rule targeting
label.req, the old login pattern, so a.reqspan inside a label rendered grey. It is the canonical.fld .req(red) now. A.fldblock replaced the last.inptextarea. Four<td>selector hooks becamedata-*attributes, since they were hooks rather than styling: removed.in-stat, removed.dec-retro, removed.dec-celland removed.act-cell. - Stacked containers — page-level spacing was an inline
margin-bottomon some containers only, so any container without one abutted its neighbour. One rule spaces them, with the last child exempt.
Lint: 0 errors / 0 warnings, baseline entry removed (zero tolerance).
WO-BOM / assignee + pagination pass (2026-08-04)
Section titled “WO-BOM / assignee + pagination pass (2026-08-04)”Six scoped changes on top of the swept flow:
- Multiple team members per step. A procedure/work-order step now assigns a
multi-select of the facility roster (
ROSTER, the My-Facility team minus managers), not a single person. The Manage assignees.dlg(#dlg-assignees) renders a roster checkbox list per step (.asn-step›.asn-step-h+.asn-gridof.asn-optcheckboxes) and works for both a work order (openAssignees(), persisted onw.stepAssignees) and a procedure (openAssignees('proc'), persisted onmeta[i].assignees);saveAssignees()reads the checked boxes and re-renders. Each step card shows “N assigned” with the set of names — the procedure step card on a new.sc-wholine, the WO procedure-shape card inline in.shape-counts. Activation readiness (“has no assignees”) is satisfied once ≥1 member is assigned ( procReadycountsst.assignees.length); the readiness Fix link (fixAssign) assigns from the roster. proc-detail gains a Manage assignees entry in its Actions menu. - Pagination on every table. All twelve
.tbltables carry the canonical.tbl-footpager —.lbl“Showing X of Y” (left) +.pager(‹ Prev · numbered · Next ›, right). Representational (page 1 active; other buttons toast); the.pagerCSS was copied from the starter template. The dashboard materials-readiness table, which had no foot, gained one. - Work Orders page head. The
....icon-actionbeside Export was removed; the head now holds only Export, Plan day and New work order. - Deprecate a procedure. The proc-detail Actions-menu Deprecate now runs
deprecateProc()— sets the version’s status to Deprecated (badge b-grey), updates the list row and the Deprecated filter, and toasts. It is reversible: a Deprecated version shows a Reactivate this version button gated by the same readiness checks. - “One step per line” creates real steps.
saveProc()parses the build-steps and QA-steps textareas (_procSteps, one line per step) into the procedure’sbuild/qaarrays; the created Draft row shows the correctN build · M QAcount and the detail renders each line as a real.step-card. - No Deprecated procedure in a new work order.
openNewWO()rebuilds the#nwo-procselect from Active procedures only, so Deprecated (and draft) versions are never selectable.
Rev 7 — configurable serial capture + WO-create BOM (2026-08-06)
Section titled “Rev 7 — configurable serial capture + WO-create BOM (2026-08-06)”- Serial capture is authored per BOM component, not hard-coded to BMS. The Edit BOM dialog (
#dlg-step-bom) gains a Capture serial checkbox per line, writingcapSerialontometa[i].bom[](toggleStepBomSerial/saveStepBom); flagged components read “· captures serial” on the step card. BMS is seeded onto the Battery Pack procedure’s last build step (ensureProcMeta→BMS with accessories,capSerial:true) so battery WOs behave exactly as before. - The Units Captured serial column (was “BMS ID”) and the assembly record (
#rc-bms-row, label#rc-bms-lbl) are driven by the procedure’s capture-serial components (captureSerialComps/serialLabelFor/compSerialFor) — any flagged component, not just BMS; a BMS line still reads “BMS ID”. - QA confirms the serial: if any build step captures a serial, the QA step card shows a Serial confirmation → Confirm
block (QA steps still carry no BOM). Operator inputs on the line, QA confirms it tallies. - New work order shows the components needed — after the assembly procedure is picked,
renderNwoBomlists each component’s have/need (perUnitBom× qty), flagging capture-serial components. The Team field was removed from work-order creation.
Rev 8 — WO created-by + Forge form naming (2026-08-07)
Section titled “Rev 8 — WO created-by + Forge form naming (2026-08-07)”- Work Orders — added a Created by column (
injectWOCreator,WO_CREATORmap over the manager default) tracking the user who created each WO. - The WO-create button keeps its name; only the form title reads “New Forge work order”. The create-form sub reads “Create a work order for the floor” (location dropped, since it can change).
Rev 9 — per-step Instruction + Acceptance criteria (2026-08-10)
Section titled “Rev 9 — per-step Instruction + Acceptance criteria (2026-08-10)”- Assembly-procedure steps gained Instruction and Acceptance criteria free-text alongside the existing BOM / Checklist / Assignees (
ensureProcMetaseedsinstruction+acceptanceper step; drafts start blank to show the authoring affordance). The step card shows both as.sc-parasections above Bill of materials, and the edit row leads with Edit instructions. - Edit instructions dialog (
#dlg-step-instructions,openStepInstructions/saveStepInstructions) edits Step name · Instruction · Acceptance criteria — the same per-aspect dialog pattern as Edit BOM / Edit checklist (rendered in DS canon, not the tabbed uat layout). Saving re-renders the step card; renaming here renames the step.
Investigations (v-investigations)
Section titled “Investigations (v-investigations)”The Product Manager (Tricycle Hardware) raises investigations off their own analysis and assigns each one to a named owner. This console is where the assignee works it. Assigned to me lists ref, subject, who raised it, severity, how long it has been running (flagged red once it passes the limit the PM set) and status. A row opens the record: scope, the full activity log naming who did what and when, and the actions Start work, Add finding and Close investigation - which will not resolve without both a root cause and the action taken. Findings and the closure are what the PM reads back. Nothing on this page operates a work order.
Goals (dashboard)
Section titled “Goals (dashboard)”The delivery target is set by the Product Manager, not here: 400 battery packs by 31 Aug. The dashboard carries the same figures the PM sees, so both sides read one number: progress with a marker for where we should be by now, delivered against what is left, the rate we are running at, the rate needed from here to land it, where the current pace finishes, and the split by line so it is clear where the output is coming from.
Several goals can run at once, so this page renders one card per goal the PM has running for this unit, each with its own target, deadline, delivery owner and state - not started, on track, behind pace, met or missed. A met or missed goal keeps its card (delivered against target, how far over or short, the average a day, the window) until the PM closes it; closed goals drop off this page. Goals are set and closed on the PM console, not here - the delivery owner reads them and delivers against them.
The table carries the full filter set - search, status, severity, a from/to date on when it was raised, and a clear - and its summary cards (active, not started, in progress, running long) sit clear of it. The owner can export a findings pack from here, not only from the PM console. When the PM raises one and assigns it, an assignment notification lands in this console’s own notifications - naming the ref, who raised it and what it is - so a flagged investigation is not missed among the personas who share it.
A goal leaves the live list the moment it finishes, so the dashboard shows only what is
still running. Finished goals sits underneath as a table — reference, target, delivered,
result, window and, for a goal the PM voided, the reason they gave. pgDone decides which
list a goal belongs to and pgResult states how it ended: met — +12, 190 short, or
voided. Nothing is overwritten and nothing disappears; a voided goal keeps its record and
its reason on this side too, so the person delivering it sees why it stopped.
The strip and the page do different jobs
Section titled “The strip and the page do different jobs”On the dashboard, one line. Whether anything is behind pace, and which goal is
furthest behind — 5,000 conversions · 432 behind. The whole strip is the click
target. The goal cards are no longer on the dashboard.
On the page, the work. A roll-up across the live goals (running, delivered, still to go, met), the live goal cards, and the finished-goal history. A finished-goal row opens that goal, including the reason the PM voided it.
One name: Targets
Section titled “One name: Targets”A delivery push and a standing service level are both the thing you are held to, so a persona does not have to learn two words for it. The nav item, the page, the strip and the page copy read Targets on every console that carries either. What each console measures is unchanged: Production and Retrofit still track delivery against pace, and a finished one still moves to Finished targets with its result and, where the PM voided it, the reason.
The tables
Section titled “The tables”Two things came out of driving the runtime probe to zero on this console.
Wide tables fold, they do not shrink. A qualifying field goes under the field it
qualifies as a .td-sub: the category under the item it classifies, the serial under the
tracking mode that produced it, the location under the person at it, the date a return
was actioned under the outcome. Nothing was dropped to make the count.
A table whose rows have nothing behind them says so. A breakdown by line, a log of
what changed, the line items on one note, an admin list whose rows carry their own Edit
and Disable links — none of those has a record to open, and demanding a row handler
would only produce one that goes nowhere. Those tables carry
data-canon="allow-static-rows" with the reason written beside them. Where a record
did exist, the row opens it instead.
The page spaces its own sections. Each section used to carry whatever margin its own
atom happened to have, which left the summary strip 4px from the section under it here
and 0px on the head consoles. #goals-page now zeroes its
children’s bottom margin and put a single 20px between them, so the gap does not
depend on which atom comes next. The two tables also stopped declaring an inline
margin-top, which would have won against the page rule.
The card says what moves the number
Section titled “The card says what moves the number”Each target names the work item it delivers, drawn from what this console records rather
than free text, and states the record set its progress is counted from — Counted from retrofit orders closed at QA. The finished-target detail repeats it, so a closed target
can still be checked against its source.
The new-item request says what it is for
Section titled “The new-item request says what it is for”The form enforced both of its rules and stated neither. An exact catalog match already
put the name preview into its exact state, disabled Send for approval, and
submitNewItemReq refused even when called directly — but a requester only discovered
that by tripping the warning, and only learned who approves by reading to the Approval
line card at the bottom. A line above the form now says it: this is for an item not
already in the catalog, an existing one blocks the request and offers the existing item
instead, and a new one goes to the named line manager before Procurement can buy it.
Progress is counted, not typed
Section titled “Progress is counted, not typed”done was a seeded number, so nothing about it answered to the window, the model, or any
work actually recorded. DELIVLOG is now an output log of closed work orders — 678
records — each carrying what it delivered, for which model, on what date and how many
units. delivDone sums the records matching a target’s deliverable, its model and its
window up to TODAY_ISO; delivElapsed derives the days elapsed from the same dates. So
narrowing a window drops the figure (G-114 goes 321 to 132 when its start moves to
1 August), switching the model changes it, and Any model sums both.
The segments make the unit ramp across the year, which is why the same deliverable pays out differently in different windows — and it is why a target that was authored to look on pace can come out behind once the number is actually counted.
Voiding a request, and who did it
Section titled “Voiding a request, and who did it”A request can be withdrawn only while it is still waiting on a decision. pending offers
Void request; created and rejected offer nothing, because the decision stands. The
confirm names the request, the quantity, who was waiting on it and when it was raised, and
says plainly that the item is never created and it cannot be undone. On confirm the record
goes to voided, the list row and its badge follow so the filter and the counts agree, and
the approval line reads Withdrawn before approval against the person who did it.
The retrieval trail names the signed-in user the same way: retrActor reads the topbar
rather than having a name written into the logger, so it follows a role switch and records
the role alongside the name.
The confirm dialog first shipped with .dlg-footer and .btn-danger, neither defined in
this file — the half-transplant web-undefined-class exists to catch. It uses the local
.dlg-foot atom, and the canonical .btn-danger rule was copied in beside the outline
variant rather than invented.
The request page had no page gutter
Section titled “The request page had no page gutter”Every view in this console sits inside .pg-wrap, which carries the canonical
padding: 28px 40px 64px. The new-item request section had been placed as a direct
child of .main, before the wrap even opens — the only view in the file outside it. So
it lost the 40px side gutter: its content started at 248px, flush against the sidebar,
and ran to 1265px, where every other page stops at 288 and 1225. Moving the section
inside the wrap was the whole fix.
The approval line no longer leaves an empty column. It was a 337px card in a 320px
column beside a 1023px form, so two thirds of that column was dead space. Sticky is not
available here: the console sets body{overflow:hidden auto} while the HTML element is
what actually scrolls, which makes body the sticky scrollport — and body never moves, so
a sticky item can never engage. The card sits below the form as a full-width section
instead. The form gets the whole 937px, and the page reads in order: name the item, say
what is needed, then see who has to approve it.
The approver is chosen, and the approval line stays beside the form
Section titled “The approver is chosen, and the approval line stays beside the form”Back to two columns. The approval line sits beside the form again. The sticky
experiment is not repeated: this console sets body{overflow:hidden auto} while the HTML
element is the real scroller, which makes body the sticky scrollport, and body never
moves — so a sticky item cannot engage here. The rule was removed rather than left
looking as though it worked.
Who approves is a decision, not a label. It was hardcoded, so every request went to
the same person and the Line manager approval step was decoration. NIR_APPROVERS lists
the people who can sign for this unit and the step renders a .filt-select over them
while the request is being written. The choice is stored on the record as by, so a
submitted request shows the person it actually went to and the select is gone. The line
under the page title names whoever is selected and follows the picker; on a submitted
record it is hidden altogether, because guidance for writing a request is wrong when you
are reading one. The void confirm and its toast name the same person.
The gutter check across the consoles. Only these two had a view outside .pg-wrap.
Measured across every view of all seven consoles: the PM and Head of Maintenance consoles run 32/32,
the five persona consoles 40/40, each internally consistent.
The approval chain is derived, not chosen
Section titled “The approval chain is derived, not chosen”The approval line renders one step per level the policy requires, naming the role and the
person from the org chart — Head of Retrofit · Ifeoma Chukwu, then VP, Tricycle Hardware · Osita, then CEO. There is no picker: the requester cannot route around their line
manager. The line above the form states the depth and the chain before they start, and the
submitted record keeps the chain it went through.
Serial capture is the receiving desk’s
Section titled “Serial capture is the receiving desk’s”A serialised line is captured in one field, not one field per unit: it takes a scan, a typed serial on Enter, or a pasted list, with Upload CSV for the packing list, removable chips and a running N of M captured. Accepting is refused until the count matches the quantity received. This is the Inventory Manager’s receiving pattern; it used to be a separate input per unit here, so twenty units meant twenty boxes and the two surfaces collected the same fact two different ways.
No lot number is asked for either. Accepting used to require one for every consumable, with an expiry beside it. The estate captures no such thing: the record of a production run is the warehouse’s batch, not a number a floor console keys at receipt, so the field, the refusal it raised and the expiry badge it drove are gone.
The approval gate moved
Section titled “The approval gate moved”Team requests used to stop here: this console carried a Team approvals segment, a history log of every decision, and inline Approve / Decline on the pending rows. All three are gone. The gate belongs to the steward of the shared team pool — see Production Inventory Manager — because that person is the one who knows whether the pool already holds the part, and because a request should pass one gate, not two.
What stays is the reading. The With the inventory manager KPI counts what is still at the gate, the status filter offers With inventory as a value the data can hold, and the request detail names who it is waiting on. A manager blocked by a slow decision can still see it; they just cannot make it.
The figure is still editable while the request waits
Section titled “The figure is still editable while the request waits”Voiding was here; correcting was not, so a requester who had asked for 240 and needed 300 could only void it and start again. The quantity is editable while the request is pending, with Save quantity beside Void request; every other field stays locked, because changing what the item is after it was sent is a different request. A decided record is wholly read-only.
The duplicate check reads the queue as well as the catalogue. A proposed name is not on the catalogue yet, so the catalogue check could not see a name already waiting on a decision, and the same request could be sent twice for the approver to notice. It now reads Already requested as ITR-0044, waiting on approval and will not send.