Skip to content

design

Persona surface for the Head of Maintenance who runs a cluster’s entire field-maintenance operation. Web flow, canonical Atlas shell (1440). Product wordmark Argo, sub Tricycle Hardware. Boots directly to the Command Centre — no login gate.

Chukwuemeka Okeke · Head of Maintenance. Runs every bay, dispatch, schedule and campaign in the Ikeja cluster, and holds the sole authority in Argo to promote, merge or reject a taxonomy code. Resolves every escalation that reaches the Head of Maintenance: blocked work orders, replenishment requests, endorsed taxonomy proposals.

Each page is rendered from JS by RENDER[pg]() into a #pg-* container that nav() toggles, rather than a static .view section. The crumb is set from the rendered .pg-title after render, so the two cannot drift. Deep-linkable via ?scenario=<slug>.

Sidebar: Command Centre (#pg-dashboard) · Tricycle cases (#pg-dispatch, the internal key stays dispatch) · Bays (#pg-bays) · Retrievals (#pg-retrievals) · Scheduler (#pg-scheduler) · Campaigns (#pg-campaigns) · Taxonomy (#pg-taxonomy) · Inventory (#pg-replenishment) · Reports (#pg-reports). Notifications (#pg-notifications, topbar bell → openNotifications()) and Settings (#pg-settings, account menu) sit off the sidebar, per the shell. Logout lives in the account menu.

Inner drill-down pages carry a .pg-back and their own record-identity .pg-sub: Bay Detail (#pg-baydetail), Retrieval (#pg-retrieval), Schedule Detail (#pg-schedule), Campaign Rollout Board (#pg-campaign), Proposal Detail (#pg-proposal), Cohorts (#pg-cohorts, reached from a Cohorts button in the Scheduler page header, not a sidebar item). They open on row click and keep the parent sidebar item active.

A stamp with no clock cannot be reconciled against a paper record, and half the tables printed the day alone. rprDay renders Wed 19 Aug 2026, 09:12: a seed that carries a time keeps it, and one that carries only a day gets a stable working-hours time derived from that day (rprClock) rather than 00:00, which reads as missing data. rprBucket keeps the day alone for a period — the By day breakdown on the parts record — because a bucket is not a moment.

Four .pstat cards: bays running (with the ones not created yet named as such) · load vs capacity across the running bays, in hours · technicians · the oldest backlog of any bay. The page used to go straight into the table, so those four were things a reader added up per row. A bay with no capacity configured shows a dash for its load rather than a percentage of zero.

The bays carry Argo’s names. The two this console shares with Argo are Metro Bay and Honda Bay, the names every other console prints; the canon sweep had renamed them by their location, Idimu and Egbeda. The two bays the sweep added, Alimosho and Ikotun, keep the names it gave them.

An on-hand figure is only as good as the last count

Section titled “An on-hand figure is only as good as the last count”

The part record carries a Stock counts tab: what the record said, what was on the shelf, the variance, the bay, who counted it and why (cycle count · month-end count · spot check), newest first. The record printed “last counted” per bay with nothing behind it.

A record longer than a screen is a set of sections to choose between, so it carries a .pseg strip over .rec-panel panes and shows one group at a time (segPanels · detailTab). The canon sweep tabbed the repair record, the battery case, the bay record and the dashboard that way and left three records as single scrolls of about 1150px: the tricycle case now reads Overview · Timeline · Vehicle history, the stock part reads Overview · Units on hand · Stock counts · Movement history, and the parts-replacement record reads Overview · By rider · Every replacement. A consumable has no serials, so its Units tab says so rather than showing an empty table. One component, so the console has one way of showing a record.

The movements table is called what the Inventory Manager calls it — Movement history, not Assignment history, for the same rows.

  • Command Centre — the Fleet Status Wall (a .tbl-card table, a row per vehicle, ordered by bay then by risk, each band a .badge with the reason beside it), a KPI band of four .pstat cards with sparklines: fleet-ready, open work orders, median time-to-resolve and repeat rate · vehicles back for the same fault inside 30 days, the number the recurrence watchlist and the Bundle R2 investigation are both about. The wall shows the vehicles a bay has to work. A healthy vehicle needs nothing from a bay and healthy is most of the fleet, so it is a count in the card head beside the at-risk badge and the rows are the ones with work on them. Then an opened-vs-closed 30-day burn-down (dualChart, hover readout), the operations feed and a fleet/cohort watchlist with bars. A work-order priority legend (“from Argo”) explains P1/P2/P3; the wall band is derived from the vehicle’s open work-order priority — Critical = P1 open, At-risk = P2 open, Watch = P3 open, Healthy = no open WO, Unknown = telemetry dark (vehBand() / pBand()). The wall shows the vehicles a bay has to work. A healthy vehicle needs nothing from a bay and healthy is most of the fleet, so it reads as a count in the bay heading beside the at-risk badge, and the chips are the vehicles with something on them. A bay with nothing open says so; a bay with no vehicles yet says that instead. The healthy count on the Command Centre KPI band is unchanged.

  • Tricycle cases (the Dispatch page title, internal key still dispatch) — a page-level .pseg tab strip sits directly under the .pg-head with four case-type tabs: Repairs (caseTab='repairs') · Battery (battery) · Oil change (oil) · Field escalation (field). There is no separate Unclassified tab: an untriaged repair is still a repair, so it sits in Repairs alongside the triaged ones. setCaseTab() re-renders the page; the strip shows on every tab so the user can switch back. Oil change, Field escalation and Unclassified each render caseTableBody(type) — a status-summary .pstat card row (All cases / Open / In progress / Resolved / Escalated, counts over the search+date-filtered set, each card clickable to filter by status via caseCardFilter) above a .tbl-card whose .tbl-card-head carries a .filt-search (case id / plate / rider / fault), a status .filt-select (All / Open / In progress / Resolved / Escalated) and a From / To date range on the case raised date. Columns: Case · Raised · Rider · Plate · Last mileage · Location · Sub-type · Status (rows carry a plate so Rider and Last mileage are mandatory). Search / status / date handlers re-render only <tbody id="case-body"> so inputs keep focus. A row opens the case detail (#pg-case, openCase): a .pg-back to the cases page, a .grid2 of The case (Raised · Raised by · Issue type · Sub-type · Last update) and Rider and vehicle (Rider · Plate · Last mileage · Location · Detail · a Keke can move badge — green Yes / red No), then a full-width Reported fault card and a State timeline (.tl, from caseTimeline — Raised → Triaged → In repair → Resolved / Escalated, tracking the case’s audit trail). Cases live in CASES (oil / field, caseBy / issueLabel). One vocabulary across all four tabs. Every tab is the same eight columns in the same order, date last: Case ID · WO ID · Plate · Rider · Fault · Priority · stage · Status · Created. A case id and a work-order id are different things — the WO is raised after triage, so an untriaged record shows its case id and a dash for the WO (WO_CASE / BAT_CASE map each work order back to the case it came from). Priority and stage share one cell (priStageCell) as one coloured pill plus text — the priority keeps its badge because that is the scanning signal, the stage reads as plain text, because two competing pills in one cell did not read. The table carries the fault class only; the complaint is on the detail. The age reads in red under the date, The created date range sits above the summary cards, not in the table head, because it scopes the cards as well as the rows. Odometer is back on Oil change and Field escalation, in the WO ID slot: a case of either kind never raises a work order, so that column was a dash on every row, and the table stays at eight columns. An age filter (under an hour · under a day · 1 to 3 days · 3 to 7 · over a week, ageInBand) sits with the other facets on every tab. Two axes are kept apart deliberately — Status says where it stands (Open · In repair · Blocked · Resolved · Closed — Resolved is not the end: the repair is done but the tricycle is still on the floor, and it only becomes Closed when someone marks it picked, via a Mark as picked action that records who collected it. A resolved record therefore reads “awaiting pickup 3 days”, a closed one “closed Tue 28 Jul 2026”, and the two are counted separately so work that is finished but uncollected cannot hide inside a completion number) and Stage says who holds it (Awaiting triage · Awaiting booking · With technician · With Maintenance Supervisor (fault validation at triage) · With Head of Maintenance · With Dispatch · Closed). There is no repair-validation gate. The Maintenance Supervisor works the repair rather than receiving a technician’s submission for it, so a work order in repair is with its technician and the supervisor signs return-to-service at the end. The gate-2 stage, the “submitted for repair validation” timeline row and the “awaiting sign-off” row on the record all came off with it, along with the Maintenance Supervisor queue column on Bays and the queue row on the bay record: there is no queue to be deep. The KPI band measures repeat rate where it used to measure first-pass validation, because there is no first pass to score without a submission and no send-back to count. A bay that is not created yet has no capacity, so its load reads as a dash rather than a percentage of zero. A five-card .pstat row partitions by status on every tab, Resolved included, so finished work is visible and countable rather than absent. The created stamp names who raised it on all four tabs: a repair by the Maintenance Supervisor at that bay, a pack fault by Argo’s telemetry, an oil service and a roadside call by the person the case already records. A stamp with nobody against it is one a bay cannot reconcile with anyone.

    Complaint and fault are different fields. The complaint is the note the case arrived with, in the reporter words; the fault is what was selected on Argo at triage, from the taxonomy. An untriaged case therefore has a complaint and reads Not selected yet for its fault. The fault cell carries the selected value with the complaint clipped beneath it (faultCell, clip), the full note sits on the detail as its own Complaint row above Fault | selected on Argo, search matches both, and recurrence counts on the fault only so no repeat is ever inferred from the wording of a note.

    Resolve and remind reach every tricycle case, not just repairs. actRec normalises a work order, a battery case and a tricycle case into one shape — waiting is the Maintenance Supervisor gate on a work order and the stage on a case — so Mark as resolved and Remind Maintenance Supervisor are written once and offered on Oil change, Field escalation and untriaged records too. A case has no pickup step, so resolving one closes it: the dialog says so, the resolution note lands on the case record as a Resolution row, and the reminder lands as Maintenance Supervisor reminded beside it. Both buttons retire once the case is resolved.

    The status cards partition their tab. They did not: the case tabs’ last card counted Resolved, a value caseStatus never returns — a resolved case reads Closed — so the card sat at 0 while closed cases existed, and Battery lost its picked-up cases the same way. The case tabs now end on Closed; Battery carries both Resolved · awaiting pickup and Closed (picked), as Repairs does. Mark as picked works on a battery case now, too — the dialog looked the record up in the work orders alone, so the button rendered and did nothing.

    Nothing from the workbench was dropped. Unblock, Approve replenishment, Reassign and Refer back keep their dialogs and required resolution notes, and the blocked banner (.cap-warn, reason plus escalation plus paused resolve-clock) is back above the actions. Mark as resolved sits beside it, offered on the same pending-verification records, and takes a required resolution note that lands on the Resolution table. Remind Maintenance Supervisor is: offered only while a record sits on a validation gate, it names the field validation engineer for that bay from STAFF, shows the gate and how long it has waited, takes an optional note, and on send emails them and raises an in-app Argo notification. What was sent is recorded as an Maintenance Supervisor reminded row on Assignment, so a second nudge is a visible decision.

    Two events, not one. A case being created is not the same event as the fault being captured at triage, so both dates are held (WO_CREATED / BAT_CREATED beside the work order’s own opened) and both show — on the row’s Created column and as separate Case created / Fault captured stages on the state timeline. Every date names its weekday (rprDay, rprWhen: “Tue 18 Aug 2026”), and anything not Resolved carries its age as a .td-sub under the date, and the age scales with how long it has been: minutes under an hour, hours under a day, then days (ageLbl, off a real RPR_NOW moment and created values that carry a time). A Resolved record shows its closing date instead.

    Recurrence on the row. The fault cell carries a repeat badge — 1×, 2×, 3× (repeatOf / repeatBadge) — counting how many times that same issue has been raised on that same vehicle, against PRIORS, the earlier closed occurrences. Those closed records also appear as Resolved rows and in the vehicle history, so a 3× can be opened and checked rather than taken on trust.

    Repairs is one population. A triaged repair is a work order (WOS), an untriaged one is a case still awaiting triage (CASES of type unclassified), and a finished one is a prior; rprUnified() normalises all three into one row shape. Filters (rprF, rprSetF) are a .filt-search over case id, plate, rider, fault and stage, plus triage state, status, bay and priority .filt-selects and a Created From / To range; all compose, and the bay lens (LENS) scopes the table. A row opens its record: a work order to #pg-repair (openWO), an untriaged case to #pg-case (openCase), a closed one confirms it is closed.

    The detail is two columns wide (.grid2, stacking under 1000px) so a record is not one long scroll; vehicle history and cross-bay capacity run full width beneath it.

    The list tables inside a record search and page in place. dataTbl(id, title, heads, rows, opts) registers a table in DT and renders body, foot and pager from that state, so a keystroke rewrites the rows without rebuilding the card — the field keeps focus. It matches on the row text with the markup stripped, so a plate, a WO id or a technician all find their rows, and an empty result names what was searched. Vehicle history (vh-case / vh-rpr / vh-bat — one id per record page, never sharing the DOM), By rider and Every replacement each carry a .filt-search; By day pages without one. A table with no rows shows neither search nor foot, because the empty row already says so.

    The field-and-value sections stay plain. rprTbl(title, heads, rows, empty) builds every section, so a work order reads as The repair · State timeline · Evidence · Parts replaced · Assignment · Vehicle · Resolution · Vehicle history · Cross-bay capacity, and a case as The case · Rider and vehicle · State timeline · Vehicle history. Section set follows the live console; the layout is DS canon, not its card look. Vehicle history (vehHistoryTbl) lists every earlier repair, battery case, service case and closed prior on the same vehicle — or on the same rider where they changed vehicle — with the current record left out. That table is why the old Recurrence chain · View in PM row was dropped from Assignment: the history shows the records themselves, on the same page, where the link only raised a toast. Every list table pages. pageSlice / pagerHtml / footLine give each one a .tbl-foot reading Showing 1–12 of 25 repairs beside a .pager (Prev · a windowed run of page numbers with … · Next), fifteen rows to a page. Changing any filter returns to page one, so a filtered set is never read from page three of the previous one. A single page draws no pager, and a single record reads singular. Nothing is capped silently — the foot always states the total behind the page. The actions row is unchanged (Unblock / Approve replenishment / Reassign / Refer back), Reassign and Unblock still take a .dlg confirm with a required resolution note, and capacity conflict still informs without blocking. .badge carries white-space:nowrap so a long status never wraps inside its 20px pill and clips — the file was the outlier against 30 other Atlas flows.

  • Battery (caseTab='battery') — battery-pack cases on the same Tricycle cases page, in the same table shape (#bat-body, batRows): Case · Pack · Plate · Rider · Fault · Bay · Priority · Status, with the pack model as a .td-sub under the case reference. A four-card .pstat row partitions it (All battery cases · New · In repair · Blocked) and batF / batSetF2 drive a .filt-search over case, pack, serial, plate and rider plus status, bay and priority selects and an Opened From / To range. Each case carries a battery id, serial, pack model, cell configuration and fault; a pack may be fitted to a vehicle (plate + rider) or in stock. A row opens #pg-battery (openBat), whose detail is the same table set — The battery case · State timeline · The pack · Evidence · Parts replaced · Assignment · Resolution · Vehicle history · Cross-bay capacity — keeping the Unblock / Reassign / Refer back actions. BATWO holds the data; it was referenced by bat* but defined nowhere, so the page threw on render before this pass.

  • Scheduler — a Day / Week / Month .pseg planning calendar (.cal, one lane per active schedule) shown side by side with a compact Schedules list (.sc-split). The granularity toggle, ‹ / › period navigation, a live date-range label and a Today reset live in one toolbar beside the status/bay filters (calOff / scAnchor() / calLabel()); an Expand toggle widens the calendar full-width. At the foot of the page sits the schedule roster (occCard(null)), and each Schedule Detail carries the same table narrowed to itself (occCard(s.id)) — one renderer, two hosts, keyed off _occHost. Columns: plate · rider · phone (riderCell() / telOf(), the number on a .td-sub) · home bay · schedule · since last service · next due · status, actions in the row menu. A Schedule Detail drops the schedule column, because a detail page already knows which schedule it is, and puts Serviced at in its place: the bay the occurrence actually went to, marked away from home where that is not where the keke belongs. The list does not carry it — where one occurrence was worked is a question about that schedule, not about the fleet. The intake was on that sub-line and is not what anyone acts on from this table: chasing a rider to a bay starts with ringing them. It is still on the send sheet, still on the Cohorts page, and still one of this table’s filters. The planning calendar reads the same occurrences (calLanes() / calCols() / calMark()): every mark is a real occurrence placed on scAddDays(scBase(),occDays(it)), carrying its state, four to a cell then +N. What it replaces was a literal map of weekday indices, occ={'sc-1':[[1,'due'],[4,'proj']],...}, with no link to SCHEDDUE at all: it drew a mark for a paused schedule, three for a condition schedule that has none, kept a lane for a retired one, reused the weekday index as a week index in month view, and returned byte-identical lanes however far you paged the date nav. Lanes are occLive() schedules only, so a schedule that generates nothing has no lane. Seven columns: the probe holds tables under eight, and what the rider’s own line carries belongs with the rider it describes rather than in a column that shrinks the type in all the others. The table pages through the flow’s own PER_PAGE / pageSlice() / footLine() / pagerHtml(), with a .tbl-foot stating the page against the whole filtered set; occClamp() is the single place the page is bounded, so the tbody and the foot cannot disagree after a filter shrinks the set. Send due to bay counts and acts on the filtered set, not the visible page. Every occurrence keeps its row for the whole cycle (occState(): past limit, overdue, due, projected, sent, serviced), ranked most urgent first by occRank() then occDays(). A deferred row carries its come-in mileage and its reason (occNote()). The predecessor is the lesson: dueSoonItems() collected only Due/Sampled, so a row left the table the instant it was dispatched, and dueSoonCard() returned '' when the list emptied — so “Dispatch all” deleted the whole card and every trace of what had just been done. Narrowing is the filters’ job, never the actions’: search over plate/rider/intake/schedule/bay plus FILTERS.occ (status, schedule, bay sent to, onboarding cohort) and an Export. The tbody/count/send-all ids carry an occSfx() host suffix, because both pages stay in the DOM once visited and a bare occ-body put two of them in the document. Cross-system feed (documentation, not built here): the schedule authored in this console is the rule. When an occurrence comes due it emits a work order into Argo (per the schedule’s “when an occurrence comes due” choice — held for a Maintenance Supervisor or auto-created), and that work order then runs the standard lifecycle (NEW → IN_REPAIR → BLOCKED → validated/closed) with execution on the Forge floor app. This console tracks the occurrence up to the send and monitors it; Argo/Forge owns it through the lifecycle. A KPI band sits above the toolbar: Overdue · Due today · Due this week · Due this month · Spare capacity · Sent to bay, in one pass over occAll() plus one over BAYS. Overdue counts past-limit occurrences too, and its sub-line states how many are past the limit. The three Due tiles count distinct vehicles with the hours they need, off the same day placement as the calendar. Spare capacity is cap - load across bays, turning red and naming the bay when one is over. The two tiles they replace, Serviced on time 91% and Planned work done 88%, were string literals sitting where the page’s own second question belongs: the PRD gives the Scheduler “what is coming due, and do we have capacity for it”, and the capacity half was answered nowhere while the numbers sat unused in BAYS. A schedule may carry dual “whichever-comes-first” triggers (trig2 / phil2, rendered via trigLabel(s)), set in the builder behind an “Also trigger on a second rule” checkbox. Usage occurrences are meter-driven: usageProj(plate, threshold) reads current mileage + kmday to project a due date and per-day rate, shown under Next due / Last mileage and driving the In cell and overdue check. Each schedule has one advance notice (lead, days, via schedLead()): an occurrence within that many days of its due date badges Due, beyond it Projected, past its due date overdue. There were two fields for this and they disagreed: grace did the work while being labelled “Counts as late after n days”, which is not what it does, and lead carried the right name, was the editable one, and was read by nothing. grace is gone. Per occurrence, Defer writes to a SCHEDLOG audit surfaced in a Schedule activity card on Schedule Detail. On an ad hoc schedule it takes days plus a required reason and moves the occurrence’s due date, not just its day count, since that date is what the roster ranks and reads by. On a distance schedule it takes a come-in mileage and a required reason (below). There is no Skip.

    One of these schedules is enforced by the vehicle, so the console reads its ladder rather than inventing one. Telemetry publishes exactly one signal: distance since the last service, which the VCU works out from its own odometer (kmSince(), seeded as svc beside each vehicle’s lifetime km). A schedule riding it carries three thresholds. warnKm 4,500, where the DU screen plays a voice prompt and raises a red unserviced icon that stays lit until the service is signed off. dueKm 5,000, where the prompt plays a second and last time. limitKm 5,200, the service limit: past it the rider’s next battery swap is blocked. Passing it does not lock the vehicle, so the rider can still ride to a bay. The two prompts are the VCU’s and the console reads them; the limit and its swap consequence are Rechaj’s. ladderState() reads those instead of a date, which puts Past limit (pastlimit) at the head of OCCORDER, above Overdue, and ladderNote() carries the consequence onto the row: how far an overdue one is from the service limit in km and in days at the rider’s rate (kmToLimit() / daysLbl()), “past limit · next swap blocked · clears on return to service or deferral” on one past it, and that a warned one has already been told. occSev() turns the same ladder into the P1/P2/P3 a scheduled work order is raised at, since telemetry does not set those and the thresholds are the only honest ranking available. The Schedule Detail’s Definition card states the limit as Service limit (schedNotice()).

    Defer on a distance schedule takes a come-in mileage (schedDeferDlg() / doSchedDefer()): the odometer reading at which the rider is to bring the tricycle in, prefilled a week of the rider’s rate above the current odometer, which the sheet states (mileageOf()), plus a required reason. On the enforced schedule, confirming it stands for the reset command to the VCU (kmReset()): distance since last service goes back to zero, the unserviced icon clears, and any swap block for the cycle is released, because a deferral is the maintenance team’s decision and its consequence is Rechaj’s to bear. The occurrence then carries comeKm and is measured against the live odometer rather than the counter (comeState() / comeDays()): Due within the notice distance of the come-in mileage, and once the odometer is past it Past limit, the same consequence as the service limit (P1, next swap blocked), on a schedule that carries a limit. The row reads “deferred · come in at N km · reason”, next due shows the come-in mileage on the odometer with the projected date at the rider’s rate, and the activity log records the mileage. One rider, EGB-455-TYU, is seeded deferred against Honda Bay’s capacity so the Deferred filter has a row. A past-limit rider is cleared by the service recorded complete or by a deferral.

    Send to bay works on a past-limit row as on any other. Its sheet (limitBlock()) states that the rider’s next battery swap is blocked until the service is recorded complete, or, while the row is overdue, how far the service limit is in km and in days.

    What went with this: the BMS telemetry watch on CAN dropout schedule, and the five-signal condition dropdown it came from (cell imbalance, thermal derate, voltage drop, insulation resistance). None of those feeds exist. Condition triggers now build from TELSIGNALS, which holds the one signal there is, and the 5,000 km service covers all active vehicles rather than a cohort, because the vehicle counts every keke whether or not a schedule covers it.

    The Time philosophy went with them. A schedule counted in days is announced to nobody: the vehicle counts distance and speaks only in distance, so a service due on a date is enforced by nothing on the keke and reaches the rider only if someone telephones. PHILLBL now holds three, which takes the option out of both builder selects and both calendar chip rows at once, and the two schedules built on dates went with it (Brake inspection every 90 days with its 21-vehicle roster, Suspension check every 120 days), along with the second half of the drive-belt audit’s dual trigger. Advance notice in days survives, because it is what a usage schedule without a threshold ladder uses: usageProj() turns km/day into a projected date and lead decides how far ahead of it the row reads Due.

    Then Usage went too, because it and Condition were the same thing written twice: a meter reading crossing a threshold. PHILLBL holds Condition and Ad hoc, and both live schedules are conditions on the one signal the vehicle publishes. The 5,000 km service keeps the vehicle’s own ladder; the drive-belt audit reads the same counter at 4,000 km with a noticeKm of ours and no warning and no service limit behind it, which dueFromKm() and the Definition card both state honestly (“Reads as due from” rather than “Rider warned by the vehicle”). With that, lead, schedLead(), usageProj(), occProj() and occIsUsage() are gone, the builder asks for a notice distance in km where it asked for days, and an occurrence in SCHEDDUE is reduced to {plate, st}: its distance comes from the vehicle and its threshold from the schedule, so neither can go stale against the other.

    Two columns left the roster. Last mileage showed lifetime odometer beside a 5,000 km next-due, and the two counted from different zeros, so the row invited a comparison that meant nothing; it is now Since last service, which is the number the threshold is actually measured against. Bay was empty on every row that had not been sent, which is most of them, so it is gone as a column and the bay now sits under the Sent to bay badge where it is known. It remains a filter and remains searchable. The Schedule Detail’s Last done column went with the lifetime reading, for the same reason: an odometer figure with nothing on the row to read it against. occCard()’s condition guard also had to stop testing the philosophy and start testing the threshold, or every Schedule Detail would have claimed its telemetry was not connected while the Scheduler listed 26 rows underneath.

    A schedule is the service it raises. There is no task name: two fields meant the name and the service could say different things, and they did. SERVICES holds the six the service document names, the service is picked from them or typed in to add one the console has not carried before, and saveSchedule() takes the schedule’s name from it. Editing a service’s steps from any schedule changes that service everywhere it is raised, which is the point: a check is written once.

    A step is one instruction and as many acceptance criteria as it takes (svcBy() / checklistHtml() / _sbSteps). The instruction is what the technician works to and what Argo shows on the bay’s sheet; the criteria are what is ticked. A criterion is typed under the step it belongs to and a step keeps at least one. General preventive maintenance is the four named services worked in one visit, so svcRebuildGeneral() derives its steps from theirs, grouped by the service each came from: adding a step to a covered service adds it there too. Throttle carries none, because the service document gives it none, and the screen says so rather than inventing them.

    What the vehicle decides is stated, not asked. Telemetry publishes one signal and the keke enforces its own thresholds against it, so the condition trigger reads SCHED_WARN_KM / SCHED_DUE_KM / SCHED_LIMIT_KM back as a sentence instead of offering an empty box, and the notice distance does the same. The three are declared once so the builder, the ladder and the record cannot give different numbers. The whichever-comes-first second rule is gone outright.

    The eligible population can be a bay. Every tricycle has a home bay (homeBay() / bayFleet()), set for the rider rather than chosen by them: the maintenance bay at the rider’s station where that station has one, otherwise the nearest maintenance bay. That is what makes a bay a set of vehicles a schedule can name, beside All active, Cohort and Specific. The cohort picker carries both kinds, the vehicle cohorts somebody drew and the onboarding intakes a rider joined with, grouped so it is clear which is which. Where a service is actually worked is a separate fact and is recorded per occurrence.

    The send sheet opens already answered. The single sheet and every batch row start at the keke’s home bay, stated beside the picker (#sb-home on the single sheet, a Home bay column on the batch), and any row can be sent elsewhere; it then says sending away from home. Picking redraws the rows, because a note that never re-reads the row can never appear.

    The Scheduled tab (schedWoBody(), FILTERS.swo) sits beside Repairs and holds the work orders schedules raised, keyed off w.sched. It is a lens, not a population: a scheduled service is still a repair and still sits in Repairs, so no count elsewhere has to be adjusted for it. Three are seeded, with their occurrences marked sent and carrying their bay, so the roster’s Sent to bay rows and the work-order list agree on the same three. That tab has since moved to the Argo app, where a Maintenance Supervisor works the checklist: on this console the roster already answers everything it did except which checklist the service is worked to, so that joins the schedule cell and the tab is gone.

    The roster seed is one row per state, not twenty-six of two. Past limit, Overdue, Due, deferred, Projected, Sent to bay and Serviced all sit on one page, and pop dropped its (362): a count the page cannot show contradicts the rows underneath it.

    Tricycle cases gained two cards on every rail: All leads, where the rollup used to ride underneath as a chip while its own members sat in cards above it, and Over 24h closes, counting what is still standing at a bay more than a day after it arrived (over24(), off ageMin(), excluding anything closed because it has left). The label is short by necessity: at seven columns Over 24h at bay wrapped, which the probe catches. Send to bay (occSendDlg() single, occSendAllDlg() batch) replaces “Confirm & dispatch”: nothing is dispatched, a work order is raised and joins a bay’s queue, and the keke still has to be brought in. The sheet opens at the tricycle’s home bay, and all four bays are offered with their load (bayOpts(sel)); Send stays disabled only while no bay is chosen. The bay filter narrows to the bay a service was sent to. A schedule whose due is 'auto' carries autoBay, the builder asks for it as required the moment that option is picked (sbAutoBay()), Schedule Detail states it as Raised at, and schedMakeWO() returns '' rather than mint a work order with no bay at all. The sheet states rider, intake, schedule hours and why the occurrence is due, then the chosen bay’s load before/after with a .cap-warn past capacity (informed override, as in Dispatch reassignment), and a service date. The batch sheet gives every row its own bay picker (sndRows() / sndPick() / sndSum() — prefixed away from the battery module’s batRows()), with a running per-bay load summary and Send disabled until every row has a bay. schedMakeWO() mints the WO at the chosen bay, takes the VIN from the VIN map (one VIN per plate, so an occurrence never carries a stale copy) and adds the schedule’s hours to that bay’s forecast load. Sending also names the rider’s relationship manager in the toast, because that is the moment they hear about it: the service lands in their app as a case against their rider (Selene › Relationship Manager, type:'Scheduled service'), the same way a fault case does, and they see it earlier still as Services due on their home screen and a Services tab on the rider, since getting the rider to a bay is their job and they cannot wait for the work order. Activating a schedule tells them nothing and no longer claims to — a rule being switched on says nothing about any particular rider. The Schedule Builder leads with a required “When an occurrence comes due” choice (hold for a Maintenance Supervisor to confirm, or auto-create the WO), then structured per-philosophy trigger inputs (number + unit / signal dropdowns — no free text), and an eligible-population picker — All active / Cohort (pick or create one here) / Specific vehicles (searchable by plate or rider name; chips show plate, rider and the vehicle’s current mileage — the comparison for a usage/distance threshold). Activating a schedule notifies the in-scope riders’ relationship managers (stated in the toast). Schedule Detail pauses, resumes and retires from .pg-actions beside Edit — once versioning went, the Lifecycle card was one paragraph and two buttons, and one button on a retired schedule, so the card is gone and Definition has the row to itself. No versions. The Version history card was two hardcoded rows that read identically for every schedule, and the version badge, the sub, Supersede with new version and the Superseded status existed only to feed it; all of it is gone, and what a past occurrence ran under is recorded on the work order it raised. No sampling either — the investigation philosophy has moved out of the Scheduler entirely. It was the same rule written twice: campaign cmp-invc scoped “Sample 15% of Bundle R2 weekly · 8 weeks” while schedule sc-4 triggered “sample 15% weekly · 8 wks”, nothing linked them, and the schedule was Active and drawing samples while the campaign it belonged to was still awaiting a VP decision. An Investigation campaign now carries its own rule (c.sampling, shown on its vehicle table) and its own in-scope vehicles in CAMPVEH, with Sampled as the state for the ones drawn into the current cycle. PHILLBL is four types, and the roster’s states are past limit / overdue / due / projected / sent / serviced. (The earlier due-load heat overlay was removed as over-built — bay load-vs-capacity already lives on Bay Detail.) A Cohorts button in the Scheduler header opens the Cohorts page (#pg-cohorts) — a .tbl-card of every vehicle cohort (name · member count · sample plates · Edit) over the COHORTS model ({id, name, members:[plate…]}), with a New cohort .btn-pri and per-row Edit through a centred .dlg (name + searchable plate/rider multi-select with removable chips, via vehLabel). This is where cohorts created inline in the population picker are viewed and edited; the picker lists them by cohortLabel(c).

  • Campaigns — list → campaign record: definition, progress, a Rollout board reading a row per bay across complete, in progress, not started and exception (a .tbl in a .tbl-card, never a .kan column board — a rollout is a count per bay, and columns of cards made four numbers look like four queues), a vehicle-level table and the exclusions list. Campaign Builder enforces objective/scope/≥1-intervention; the routing rule shows Head of Maintenance only vs Head of Maintenance+VP. Approve is a real transition.

  • Taxonomy — the Proposed Modes queue and a read-only Reference catalog on a .pseg. A Raised-only proposal shows “awaiting Maintenance Supervisor endorsement” and is not actionable. Proposal Detail carries the append-only endorsement .tl and Approve · promote to code / Merge / Reject, each a .dlg stating its exact consequence.

Bays — Bay Operations list → Bay Detail (#pg-baydetail) shows the bay location, an editable roster (add staff from a searchable available-staff list, role Technician/Maintenance Supervisor, set lead, and per-member Make lead / Reassign / Remove — each a .dlg confirm), editable capacity and minimum staff, an under-staffed .cap-warn when the roster is below the minimum, a backlog age-distribution histogram, parts constraints, and the Maintenance Supervisor validation queue. Roster count and lead derive from the shared STAFF list via syncBay(). A bay can be created (New bay dialog: name, Area/LGA, a Nigerian-state dropdown, capacity, minimum staff) and deactivated / reactivated with a required reason; deactivated bays show a banner and a dimmed list flag. Every lifecycle and roster/capacity action is written to a per-bay Audit trail card (BAYAUDIT / bayAudit()) with actor + timestamp.

The console runs on the bays Argo actually has: Metro and Honda are live, and Ifo is listed but cannot be selected until it exists. Records that used to sit on four invented bays fold onto the two live ones rather than being deleted, so every work order, stock line and replacement still points at a real bay.

A tricycle comes off a rider, and goes back as parts

Section titled “A tricycle comes off a rider, and goes back as parts”

Retrievals (#pg-retrievals) is the maintenance end of a chain that already had a sender and no receiver. Retrieval Logistics raises a retrieval against a rider, approves it and dispatches it either into the field or to a maintenance bay; a bay dispatch mints ARGO-WO-#### against the bay it chose. Until now nothing on this console could receive one.

Three .pseg tabs, Incoming, Parts recovered and Recovery, and the bay lens scopes all three the way it scopes every other page.

Nothing recovered here is refitted at the bay. A retrofit station reassigns a kit onto the next keke because that is its job. Maintenance strips and returns: a recovered part goes back to Inventory. Nothing is refitted here, nothing is repaired here and nothing is judged here: the warehouse assesses what it receives on its own returns desk, which is where the condition vocabulary already lives. So a recovered part carries a serial and nothing else, and there is no destination to choose because there is only one place it can go.

The strip-down IS the settle. A part is in the technician’s hand while they walk the line, so a second pass to claim what they were already holding is a form asking a question it has just been answered. Closing the work order sends every recovered part, and a retrieval is therefore awaiting arrival, in strip-down, or completed.

The retrieval is a work order of its own type

Section titled “The retrieval is a work order of its own type”

Confirming arrival opens a Retrieval strip-down work order at the destination bay. It runs NEW → IN_REPAIR, goes BLOCKED while any part is held, and closes CLOSED_RETRIEVED (RETR_STWO). It never reaches IN_SERVICE or CLOSED_NOT_FAULT, because the vehicle is not going back to a rider, and a fault-quality metric that averaged strip-downs in with repairs would be measuring two different things.

Only the bay can see the tricycle. Retrieval Logistics dispatches it and then knows nothing more, so there is no arrived claim for anyone to confirm: the record asks Is the tricycle at the bay? and the bay’s answer IS the arrival. Yes stamps the confirmation and opens the strip-down work order, naming nobody: the technician is named when the bench is walked, on the strip-down, not by someone answering a yes-or-no question.

Opening the work order is itself an answer. Somebody who already has the tricycle on the bench should not have to go back and tick a box first, so Record strip-down sits beside the question on an unanswered record, and using it stamps the arrival and says on the trail that it was confirmed by opening the work order. You cannot strip a keke that is not here.

No records the answer and opens nothing, and tells nobody: Retrieval Logistics only wants to hear that the tricycle arrived, so a no is this bay writing down what it can see rather than raising something with the people who sent it. The answer is the whole of it: a bay saying the tricycle is not here is not making a case, and a required reason box was asking it to. One waiting state covers both, since a retrieval that has not arrived and one nobody has answered for are the same thing to this console: it is not here yet.

A record also says how long it has been open, counted from the moment it was sent, because a queue with no clock on it is a list.

Four .pstat KPIs that each set the filter they name: To confirm · In strip-down · Held for repair · Awaiting inventory acceptance. The last one is not a status the record holds, it is a reading of its parts, so it is a value of the same status filter (AWAIT) rather than a card that filters nothing.

Tricycles coming in reads in the shape Tricycle cases already uses, because a reader moving between the two tables should not have to relearn where the references are: Case (Retrieval) leads, bold, then WO ID in .mono beside it, then Plate · Rider · Bay · Sent with the person who dispatched it under the date · Status · Recovered, parts recovered against parts expected with a bar. A retrieval whose strip-down has not opened has no work order to name, so that cell reads as the same muted dash a case with no work order uses. Over the table: search, status and bay .filt-selects, a sent-date range, a reset that appears only once something is filtered, and an export.

The trigger is not a column and not a filter — it is why the retrieval exists rather than something the bay sorts by, so it reads on the record. Neither is a last update column: what a record last did is on the record itself, and a column repeating it pushed the table past the width where eight columns still read.

There is no Actions column. The row opens the record, and the one step the record can take next is the primary button there, so a menu beside every row was a second route to the same two places.

#pg-retrieval opens on row click behind a .pg-back that names the tab it came from, so Back walks the trail rather than guessing. Two panels: Overview — the retrieval and the vehicle side by side, then the work order and Location side by side, then the parts manifest, then the status trail — and Parts recovered. The work order had a full row to itself for five fields, so it gives half of it to Location.

Location is read off RadR, not off the bay. RadR reports a position with every trip, so the card names where the tricycle actually is as a street address, with the time it was last seen, and the address opens in Google Maps. That matters most on a retrieval nobody has answered for: a keke still on the Lagos-Abeokuta Expressway is not at the bay, and the card says so before anyone walks out to look. A retrieval that has arrived reports the bay’s own address, because that is where RadR last saw it. The strip-down had a panel of its own holding one card and a second rendering of the manifest already above it, and the trail had one holding a single card; where a record has got to is the first thing anyone opens it for, so it reads beside the record rather than behind a tab.

The retrieval card carries the retrieval, and the vehicle card the vehicle. The retrieval names itself and its work order, the rider, their 4-week efficiency, who raised it, when it was sent, the bay it was sent to and the work order status it is at. The vehicle carries only what is true of the keke: plate, chassis, fitted VCU, kit revision and odometer. A bay and a work-order state are facts about the retrieval, so they sat on the wrong card.

This is where the console holds more than the bay app does: the rider’s 4-week efficiency, the chassis, the fitted VCU, the kit revision, the odometer, and the technician and hours on the bench.

One tick stands behind the whole manifest. Nothing is recorded line by line, but the close tells Inventory to expect every unit on the list, so the actor confirms that every part came off before the dispatch will go. It is the one thing the strip-down asks that the record cannot work out for itself, and the close is blocked until it is ticked.

The trail is built from the record’s own stamps, so the trail and the record cannot drift: generated, sent, answered at the bay, strip-down opened, strip-down closed, parts settled, sent to inventory, accepted. A step the record has not reached reads as what it is waiting for. Anything the stamps cannot carry — a reported non-arrival, a hold cleared, a claim changed — lands in RETR_LOG and rides on top.

Nothing is logged by hand. Closing the strip-down mints one row per part the manifest records as recovered, each Not yet decided: no condition, no outcome, and the table says Not set rather than inventing one. A part never fitted, or that never arrived, mints nothing and is kept on the manifest with its reason.

The manifest is what the register says is on that keke, named in the catalogue the warehouse stocks by (RETR_MANIFEST), and it carries the serial of every unit before anyone touches the vehicle. A strip-down confirms or contradicts those serials; it does not discover them, and a blank serial column on a keke the register holds five units against was the table saying it knew nothing when it knew everything.

So a line nobody has ruled on yet reads Not yet recovered — a state of the part, not a state of the paperwork. The reason a part was not recovered rides under the state it explains rather than in a Note column empty on every line that went as expected, and the register’s serial stays on that line: the unit that should have been there is the whole point of recording that it was not.

A retrieval is claimed in the four conditions stock is actually held in — Unused, Refurbished, Used, Faulty (RETR_BUCKETS) — rather than a good-or-faulty pair the warehouse cannot restock against. One list feeds the settle form, the condition filter and the table.

Sending is not returning. A returned part reads Sent to inventory and is awaiting acceptance until the warehouse takes it, then Accepted by inventory. For exactly that long the claimed condition stays editable, because the claim is the bay’s and the decision is the warehouse’s; where the warehouse took a part as something other than what was claimed, the row says both rather than overwriting the claim.

The return itself is not a second lane. It lands on the store’s existing outgoing lane (HOM_RET, under the retrieval’s own reference) and in HOM_MOV with who moved it, so the Stock tab’s Outgoing to Inventory figure counts it. It is marked as coming from a retrieval rather than from the store, so a part that was never store stock never reads as store stock leaving.

Inventory (the page slug stays replenishment) carries three .pseg tabs, Approvals, Stock and Parts.

Approvals is the queue of bay requests waiting on this console, with multi-select bulk approve. Its request model follows the built Tricycle Hardware Inventory Manager the same way Production requests inventory: a bay raises a request, Head of Maintenance approves, and the Inventory Manager fulfils it with a delivery note the bay accepts (statuses Requested / Approved / Fulfilled, available per line, delivery-note ref on fulfilment) — no separate IMS abstraction. The row carries the day and time it was raised, not a bare age, and the tab leads with what is waiting on the Head of Maintenance, the units held up behind it, what has gone to the Inventory Manager, and how long the oldest request has sat. A cross-bay constraints card used to sit under the queue; it said the same thing the request rows already say, so it is gone.

Stock answers what is actually held. STOCK carries on hand per part per bay with the date that level was last counted, and STOCK_IN carries what arrived against a delivery note; issues are read from PART_REPL, the same events the Parts tab counts, so the two tabs cannot disagree. Four KPIs head the tab, and they are the work rather than the shelf: awaiting receipt, open requests, outgoing to Inventory, and units on hand (the store and the bays together, which is what the table totals). Then Parts on hand (Part with its category · Tracking · In the store · At the bays · On hand · Where it is · Last movement · Actions, stkParts) over search, bay and category filters and a reset. The bays cell names the largest holding and counts the rest, never a comma-joined list. A part row opens #pg-stock (openStock): what it is and where it sits, By bay with the date each level was counted, Units on hand for a serialized part — every unit, the shelf it is standing on and the delivery it came in on — and Assignment history, every movement behind the number, received +qty against its delivery note or issued −qty to a work order with the vehicle, rider and technician it went to, searchable and paged. The page carries the same two actions as its row, as buttons. The bay lens scopes all of it.

The Stock tab also carries the Head of Maintenance store — the disbursement path beside the direct one. A bay-raised request, once approved, still goes straight to the Inventory Manager. The store is for stocking ahead of need: repairs held for faulty returns drain a bay’s usable stock, so the Head of Maintenance requests stock from the Inventory Manager (HOM_REQ, multi-line, Requested → Fulfilled → Received), receives it into the store against a delivery note, pushes it to bays — the bay receives it into its own stock (STOCK rises, STOCK_IN logs the movement against HoM store) — and returns what is not needed to Inventory with a required reason. Every movement lands in HOM_MOV with who moved it. Push and return are capped by what the store holds.

Parts answers what has actually been consumed. It is built from PART_REPL, one row per part replaced on a repair (day, part, category, quantity, work order, plate, bay, technician). Four KPIs (replaced all round · last 7 days · distinct parts · bays) head the tab. Then the tab leads with the replacement rate, because a table of days cannot show a direction and bringing that number down is what the tab is for: a line readable by day, month or year (partF.grain) over its own date range, with the half just gone measured against the half before it and read in plain words, “Down 12% on the period before”. Under it, two tables: Replacements by part (Part · Category · Tracking · Replaced all round · Last 7 days · Bays · Last replaced, partTotals) and Replacements by day (Day · Units replaced · Parts · Repairs · Bays, partDays) — the day row names its units, the parts that went in (biggest by name, the rest as +N more, never a comma-joined list) and how many repairs consumed them, so parts-per-repair reads off the row. An unlabelled count and a count-of-kinds did not carry that. Search plus bay and category filters and a reset sit in the by-part card head and scope both tables, and the bay lens scopes them too. The date range sits in both card heads and scopes both tables, with its own Clear dates — in the table it filters rather than floating above the page. It is one piece of state (partF.from / partF.to), so setting it on either card moves both. While it is on, the first two KPI cards read Replaced in range and Last 7 days in range, because a card labelled all-time under a date filter is simply wrong, and the day card’s subtitle says in range. A part row opens #pg-part (openPart): its totals, its own by-day breakdown, a By rider table (rider · plate · times replaced · replaced · bay · last replaced, partRiders) so one rider eating a part shows up, and every replacement behind the number — day, quantity, WO id, plate, rider, bay and technician — so a count can be opened rather than trusted. Reports is a section-based sign-off builder; Settings is the canonical two-pane .settings-layout.

  • Two-pane Dispatch, not a pinned detail pane. The workbench is a queue rail + work surface grid, a layout rather than a master-detail pair, so it is never the banned side drawer or a .detail-head pane pinned beside a table. An earlier three-column version read as busy at 1440 (three narrow tracks, the middle one overflowing); the record is a page now, its actions in the canonical .pg-actions row of the .pg-head (never a detail-local .dw-actions strip), its sections on a .pseg — overview, timeline, evidence and parts, vehicle history, cross-bay capacity — which gave the detail room and cut the badge pile to status + severity. Compact create/edit/confirm use the centred .dlg; rich records use a full inner page with .pg-back.
  • Per-bay lens in the topbar (openLens() / setLens()) scopes Dispatch, Scheduler, Bays and Campaigns to one bay, toggleable back to cluster-wide.
  • Required form fields carry a red .req asterisk on the label; optional fields none.
  • Top-level page heads carry no .pg-sub; only inner pages (identified by their .pg-back) do. Table filters live in the .tbl-card-head as .filt-select, never a .pseg filtering a table.
  • Every .tbl-card data table carries a .filt-search in its .tbl-card-head, filtering that table’s rows by their visible identifiers — Bays (name, lead), Schedules (name, philosophy), Cohorts (name, member plates, rider), Campaigns (name, type), Replenishment (bay, requester, parts), Report history (period, signer), and the five vehicle tables below. The search handler sets only that table’s <tbody> innerHTML, never the page render, so the input keeps focus.
  • Every vehicle table carries a Rider and a Last-recorded-mileage column immediately after the plate — the rider via riderOf(), the odometer via mileageOf() / mileageLbl(). Vehicles that appear only in schedules/campaigns/work orders (not on the Fleet Status Wall) resolve their rider and mileage through the VEHX registry, so no row shows a blank rider. Tables: Scheduler Vehicles due, Due soon, Bay Detail Work orders at this bay, Campaign Vehicle-level progress, Taxonomy Related-evidence queue (the Due-soon header reads Plate, not Vehicle, matching the rest). Their search matches the rider too. The Cohorts tables carry neither — their rows are cohorts, not single vehicles.
  • The onboarding cohort is the rider’s intake, and it is read-only here. ONBCOHORTS ({id, name, intake, cluster, members:[plate…]}) names the group a rider joined Rechaj with; onbOf() / onbName() / onbCell() resolve it. A second card on the Cohorts page lists the intakes (intake · joined · riders · services due · View riders) with its own search, a services-due filter (FILTERS.onb) and an Export, and onbOpen() shows one intake’s riders. The distinction is the point: a vehicle cohort is a set someone drew for a schedule and can redraw tomorrow, while an intake is fixed the day the rider joined — which is what makes it worth reading a recurring fault against, and why nothing here edits it. The console covers one cluster, Ikeja (lensLabel()), so every intake carries that cluster and the table reports how much of the intake is due instead; Idimu/Alimosho/Ikotun are bay locations, not clusters, and must not be written as such.
  • Work-order priority uses Argo’s P1/P2/P3 (P1 critical, P2 high, P3 normal), not S1–S4. It is the single scale across Dispatch, the Command Centre legend and the Fleet Status Wall band derivation — the source of truth being Argo, where work orders are raised.
  • Every list view carries an Export (.btn-sec) — Command Centre, Dispatch, Bays, Scheduler, Campaigns, Taxonomy, Replenishment.

Lint: 0 errors / 0 warnings. flow-diff vs cluster-maintenance-manager: 0 gaps.

A topbar .icon-action sits beside Notifications and opens #dlg-feedback, the centred .dlg that openDlg() / dlgHead() build. Five .field blocks in order: Type (Bug, Improvement), Impact (Blocking, Slows me down, Minor), Where, Details, and an optional Screenshot file input. Where is built from PG, so a page cannot be offered that is not routable, and it preselects the page on screen, falling back to the active sidebar section when a detail page is open. sendFeedback() requires Type, Impact and Details, each with its own toast, then closes the dialog and confirms with one more.

Lint: 0 errors / 0 warnings. flow-diff vs cluster-maintenance-manager: 0 gaps.

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.

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.

The PM sets the green and amber bands on their own Analytics — Health targets tab. Until now the people measured against them could not see them, so “we are behind” arrived as an opinion rather than a number. The dashboard carries a Targets card: the measure, where this console stands, the target, and the size of the gap when it is off — over by 5.4h, under by 3 pts — coloured by the PM’s own bands, amber inside the amber band and red past it. The card head counts how many are off, so a miss is visible without reading the rows. A gap between two percentages is stated in points, not per cent.

The card is read-only here. PMTGT holds the same band edges the PM set; tgBand, tgGap and tgStatus derive everything else, so there is one definition of “on target” and no second opinion to keep in step.

A dashboard is where someone lands, so that is where a breach has to reach them; a five-column reference table is not what a dashboard is for. So the two were split the way Monitor and Analyse were split, and neither restates the other.

On the dashboard, one line. .tg-strip holds a single .tbl-card head: whether anything is off, and which one is worst. Nothing else. The whole strip is the click target.

Worst means furthest through its own band. Comparing a gap of 5.4h against one of 3 points is not a comparison, so tgFrac expresses each gap against the width of its own band and ranks on that, after ranking on band severity. Before this, “worst” was whichever off-target measure happened to come first, which named Fleet-ready when Immobilized was four times further gone.

On the page, what a strip cannot hold. Each measure against the red/amber/green bands as a bar with a marker where the value sits (tgBar), the full band definitions, and every change the PM has made to that target with the date and who made it. A row opens that measure on its own.

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. #pg-targets 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 store says what it asked for and what came back

Section titled “The store says what it asked for and what came back”

Requesting stock carried a part and a quantity and nothing else, so Inventory could not tell an urgent bay-down request from a routine top-up. A request now carries urgency, a needed-by date and why it is needed (required, since Inventory prioritises on it), and each line shows what the store already holds of that part.

Receiving is a check, not a click. The receipt lists every line as requested against fulfilled, names the shortfall where there is one, and takes the quantity actually received. A serialized part is received unit by unit, but not one input per unit: a real delivery of forty runs down a single field. A serial is typed, scanned or read out of a CSV and lands as a chip in a list that counts itself against what was fulfilled, and any chip can be taken back off. The receipt will not close until every unit received has one. The store keeps those serials, so moving a serialized part to a bay is a choice of which units go, not a number.

A delivery can be refused. Reject takes a reason (wrong part, quantity does not match the note, damaged in transit, serials do not match, not ordered here) plus the detail, nothing enters the store, and the reason goes back to the Inventory Manager. The requests table reads requested, fulfilled, urgency, needed-by and status, and a row opens the request as a record: what was asked, what was fulfilled, what was received, and how it ended. Ten serials in a table cell is a column of wrapped fragments, so the units read as their own list under the line they arrived on.

Stock arriving is Received, whichever direction it came from. A request received and a return accepted were one event described two ways, which put two words for the same state in one status column. Inventory fulfilling a request also reaches the store as a notification, which opens the receipt it is about.

The store deals with the warehouse on the warehouse’s terms

Section titled “The store deals with the warehouse on the warehouse’s terms”

Three things did not tally with the Inventory Manager side, and now do.

One catalog. A request picks from the catalog Inventory fulfils from, not the bays’ own vocabulary, so a request names something the warehouse actually stocks.

Returning stock asks what the warehouse asks. The warehouse receives a return in the bucket vocabulary and reconciles serials against what it issued, so the return form states the condition in those words (Unused, Refurbished, Used, Faulty), names the units where the part is serialized, and references the delivery it came in on, beside the reason.

Custody transfers on acceptance, not dispatch. A return stays the store’s stock, counted as outgoing and excluded from what the store can still use, until Inventory accepts it. The Returns to Inventory table reads what went back, its condition and what Inventory decided, and a declined request reads its reason back the same way.

The store’s tables follow the board’s table rules

Section titled “The store’s tables follow the board’s table rules”

All three carry a search; the requests and returns tables add a status filter and a raised-date range, and all three export what the filter leaves standing. The part field on a request is a search against the catalog, not a select to scroll, and a line naming something the warehouse does not stock is refused.

A unit picker only appears where there are units: the store knows which serials it holds, so a move to a bay names them, and a part it holds no serials for shows no picker at all rather than a dead line.

The unit of measure rides the quantity, not the part. A part is a part whether there is one or a hundred; it is the figure that is counted in rolls, pairs or pieces, so every quantity in every table, dialog and record carries its unit and no column is spent on it.

How a part is tracked reads off the table. Serialized or consumable decides how a part is received, moved and returned, so it is a column on the stock, approvals and parts tables rather than something to open a record for.

Every part on hand can be moved or sent back. The two actions sit in a row menu, and on the part record as buttons, and they name the shelf the stock is leaving rather than assuming the store: a part sitting at a bay goes to another bay or back to Inventory the same way. An allocation cannot outrun that shelf, so each bay box takes only what is still unallocated.

The Inventory tabs lead with the work, not the shelf. Approvals opens on what is waiting on the Head of Maintenance, the units that are holding up, what has gone to the Inventory Manager and how long the oldest request has sat, and every request carries the day and time it was raised rather than a bare age. Stock opens on what there is to receive, what is still owed by Inventory, what is outgoing and not yet accepted, and what is on hand. Parts leads with the replacement rate: a line that can be read by day, month or year over its own date range, with the period just gone measured against the period before it, because bringing that number down is what the tab is for.

A returned serial is reconciled against the serial that was issued. Ticking units off a list assumes the right units come back, which is the one thing a return has to prove. Serials are captured the way they are captured on receipt and each is read against what the named delivery note issued, so a unit that was never issued on it is flagged on the form and on the record.

A shelf that disagrees with the record is corrected here

Section titled “A shelf that disagrees with the record is corrected here”

Counting a shelf and finding it disagrees is the commonest thing that happens to stock, and the count history was the one table on this console computed from the part’s own numbers rather than held: it could show a count, but never one somebody had actually made. It is a record now (STK_COUNTS), seeded with counts across the store and the bays, each carrying who counted, what the record said, what was on the shelf, why, and the note that accounts for it.

Adjust quantity (openStkAdjust) takes the correction from the part record, the stock row menu or a bay’s own stock table. Where it is opened from a bay the place is already known, so it is stated rather than offered; from the part record or the stock row nothing implies a place, so it asks, each option naming what that place currently holds. It reads back what the record says, takes what is physically on the shelf, and requires a reason in words rather than a category, because the useful half of a correction is the sentence that accounts for it. The variance is stated as it is typed, so the manager sees what the figure will become before applying it.

Applying moves the figure where it was wrong and nowhere else: the store figure, or that bay’s line, never both, and writes the correction into the count history with the actor and the day. The store totals, the part record and the bay all read the corrected figure straight away.

The bay record gains a Stock panel (bayStockPanel) between capacity and work orders: the parts that bay holds with their category and tracking, what is on hand, when it was last counted, when it last moved, and an Adjust beside each one. Part names open the part record, so the bay is a way into the same stock rather than a second copy of it.

Serviced. The roster ran to Sent to bay and stopped, so a Head of Maintenance could see what had been handed to a bay and never what came back. Serviced is a state of its own, ranked after sent: the row keeps its place, carries the work order that closed it and the day it was signed off, and its next-due cell reads counter reset because that is what recording the service does. It is where a completed service is read on this console, and it answers the same question the Argo app’s Completed sub-rail answers on the floor.

Back walks the trail. A work order opened from the roster was returning you to Tricycle cases, which is where the record lives but not where you came from. pgHead() takes a backGo, openWO(id,from) takes where it was opened from, and the roster’s link passes 'sched' from the Scheduler or the schedule’s id from a Schedule Detail, so Back reads Maintenance Scheduler or the schedule by name and the sidebar highlights the section you actually came from.

Bay Detail takes a Retrievals tab, beside Work orders and built the same way (bayRetrAll() / bayRetrRows() / bayRetrSearch()), reading the same RETR records the Retrievals page reads rather than a copy of them. A bay takes two kinds of work and a Head of Maintenance looking at that bay could see one and not the other.

The roster became the rider register. Every active vehicle is on the 5,000 km service because the keke counts every one of them, so every rider has a row whether or not anything is owed: one who has never been scheduled will need a service eventually, and this is where they are tracked. Twenty-six rows, and an OCC_PER of 30 so the whole register is read at once rather than paged. pageSlice(), pageCount() and pagerHtml() take an optional page size, so nothing else on the console changed size with it.

No work-order number on it, because the register answers where a rider stands and not what was raised about them, and the work order is read where work orders are read. No Skip either: a service not done is deferred with a come-in mileage or left to the service limit.

One row per rider (occRegister()). Twenty-one riders were making twenty-six rows, because a rider covered by two schedules was getting a row for each, and a list of occurrences is not a register of riders. occAll() is already ranked most urgent first, so the first occurrence a plate appears in represents it and the rest are counted on the row. Narrowing to one schedule switches back to that schedule’s own vehicles, since that is what the filter is asking for.

The calendar carries counts and load (calCell() / calDayCap() / occCalDay()). One mark per occurrence reads at twenty-six riders and stops reading at three thousand, where a cell becomes a number with decoration round it. A cell is now 4 · 8h, toned by the most urgent state in it, with the breakdown on hover, and a Bay capacity row underneath puts the hours each column needs against the hours the bays have. calOcc() excludes sent and serviced, because those hours are already on a bay’s load or already spent, and occCalDay() lands anything past its threshold on today: late work is not a date in the past, it is work standing on this morning, and plotting it behind the period dropped it out of every view.

Two things about that capacity row are not settled and are flagged rather than hidden. It reads technicians x 8 a day from the roster, while every other capacity figure on the console reads the flat BAYS[].cap, which states no period anywhere; and it counts only scheduled services, while the forecast-load definition is schedules plus campaigns plus backlog.

The checklist is read as blocks (.ckl). Twelve steps of stacked label and value rows gave the instruction, the criteria and the consequence the same weight, so none of them had any. A step is now its number, what to do, what has to be true as a list, the measurement, part and serial capture as chips, and the failure rule set apart in its own tinted line, because that is the line that changes what happens next. The number badge inverts on --text-1 over --grey1 rather than white on --ink, which was unreadable in dark.

The checklist is the checks. It was carrying an instruction paragraph, acceptance criteria, a measurement, a part expected, a serial flag and a failure rule per step, which is a work instruction rather than a checklist, and it was written per schedule. Preventive maintenance is the same inspection every time, so CHECKLIST is now one standard list of thirty-six checks grouped by where on the vehicle they are, and a check is one line that has to be true with its pass figure in it. What a technician observes and what they fit is recorded as they work, on the work order, and the Argo app already has its own panels for exactly that.

A bay’s load is derived (woHours() / bayOpenWork() / bayLoadOf(), kept in sync by syncBay()): the tricycle cases standing at it plus the services sent to it, both costed, a fault off its priority and a service off its schedule’s hours. Campaigns are out because they have not started, and the backlog figure is out because the open work orders are the backlog and counting both stated the same work twice. schedMakeWO() no longer adds hours to a bay by hand: raising the work order is what puts the load there.

Capacity is a weekly figure the Head of Maintenance sets, per bay, against those two things. cap was a seeded number stating no period anywhere, which is why the calendar and the KPI band could not be read together. The calendar’s row now takes that week figure and gives a column the share of it that its span covers, and the Due load tile says plainly when the week does not fit: 20h to place, 11h more than the bays have free, red, beside a Spare capacity tile naming the bay that is over.

Vehicles due per day sit in the date heading. Reading it off the lanes meant adding them up, and adding them up got it wrong: a vehicle covered by two schedules on the same day is one vehicle coming into a bay, not two. colVeh counts distinct plates across the lanes on screen, which is why Wednesday can read 4 vehicles over lanes holding 2 and 4. The grid is computed once into grid before the header is written, since the header now depends on it.

A register row opens the rider (openRider(), pg-rider). It opened nothing, which broke the rule every other table here keeps. The record holds the service cycle first (since last service, next due at, daily average, odometer, swap gate), then every schedule covering the vehicle with each one’s state and its own send action, then the rider and vehicle, then vehHistoryTbl() for everything ever raised on it. Back returns to the Scheduler through pgHead()’s backGo, the same trail the work-order link used.

Swap gate on the record (swapGate() / SWAPGATE) states what the Scheduler has sent the swap gate for this rider: Clear, Service due sent or Swap blocked. It reads the same occState() as the register row on every schedule that carries a service limit, so the two cannot disagree: Due or Overdue is Service due sent, Past limit is Swap blocked, and a serviced or projected one is Clear. A keke sent to a bay keeps its ladder or come-in state until the service is recorded. The band is five cards (.pstat-row-5), and the value is a word, so it sits in .pstat-word at 600. The Scheduler sends the gate Service due, Service breached and Cleared, retried until acknowledged, and a message that keeps failing is raised to the Head of Maintenance by vehicle.

The row shows the most recent thing, not the most urgent (OCCRECENT / occRecency()). Picking the representative by urgency told you a keke was Due while it was standing at a bay with a work order open on it. Serviced and Sent come first now, then the urgency order for anything still outstanding, and the register is still sorted by urgency so the pressing rows stay at the top.

The horizons are cards (dueInSpan() / weekDue() / monthDue()): Due today and Due this week, the second carrying the five-week figure beside its hours. Due and Overdue were totals and the calendar was per day, so a week was something you added up by eye. They went in as a strip under the band first, which put “Due today 4” directly beneath “Due 4” and read as repetition, because it was. So they are tiles, the plain Due total is gone (a count with no horizon on it was the least useful number on the page), and the band is six. Counts are distinct vehicles off calOcc() and occCalDay(), the same occurrences and the same placement the cells use, and the spans anchor on scBase() rather than scAnchor(): this week has to mean this week whichever view is open and however far the date navigation has been paged.

The record’s Parts replaced panel reads woPartsOf(), which is the rule that decides a work order’s parts, lifted out of the two detail pages that each had their own copy of it inline. A rider’s panel therefore cannot list a part its work order does not.

The record’s three parts sit in segPanels(), the same panel strip every other record page here uses. Side by side only works when both sides are about the same size, and a vehicle on one schedule left a third of the row empty. The services table also lost its distance column: the counter is the vehicle’s, so it printed the same figure on every row of a table about one vehicle, and it is already the first thing the band above states. The schedule cell carries the checklist count in its place.

Due load is gone, and scDueHrs with it. It summed every outstanding service whenever it falls due and set that against a week of capacity, which is two clocks in one sentence: 20h of undated work against 9h of room read as an 11h shortfall that was not real. Nothing took its place; the hours are already on the horizon tiles, each covering a period it can be compared against.

Due this month joins them, counting the calendar month (monthDue() off the 1st to the last day of scBase()’s month) rather than a rolling five weeks, so the tile and the Month view name the same period. That meant fixing the Month view, which laid out five week buckets from the Monday before the 1st: for August 2026 that Monday is 27 July, so the grid ran out on 30 August and 31 August was not on it at all, along with the vehicle due that week. calCols() now emits as many weeks as the month actually touches, five or six.

Serial mismatch on a retrieved kit (manSerial() / manMis() / retrMisDlg() / retrDoMis(), and the per-item fields in the strip-down dialog). A kit comes off a keke whole, but the serial on an item is not always the serial the register says is fitted: a pack swapped at a swap station and never recorded is enough to break it. So a manifest item carries act, the serial actually read off the part, alongside ser, what the register claimed, and a recovered part carries both as ser and exp with mis set. Both are kept rather than one overwriting the other, because the register is the claim and the part is the fact.

It is recordable in two places, which is deliberate: per item while the strip-down is being closed, where an open mismatch field with nothing typed in it blocks the close, and afterwards from the manifest on the record, because a mismatch is usually found on the second look and sometimes by the warehouse. A correction made after the fact follows the part into RETR_PARTS, since the warehouse is holding the item and not the manifest.

One naming trap worth recording: the row action first read Serial mismatch, the same words as the badge, so an untouched row looked like a mismatched one. It reads Record mismatch, and Correct serial once there is something to correct.

Reports (#pg-reports) is the analysis, not a wrapper round it

Section titled “Reports (#pg-reports) is the analysis, not a wrapper round it”

Reports used to be a list of section names with an Add narrative button beside each one and a sign-off dialog at the top. It described a report; it did not contain one. The figures a head of maintenance actually reports — how much work came in, how it split between the bay and the road, what failed, how long it took, which vehicles came back — were assembled somewhere else and pasted in, which is exactly how a report and the console it was read off end up disagreeing.

There is no separate analytics page. A second surface rendering the same sections is a second place for the same numbers to live, and the one that is not signed is the one that goes stale. The report is the analysis: the page computes it live from the case population, the head of maintenance interrogates it in place, and signing freezes what is already on screen.

Every figure comes from MC: one row per maintenance case, carrying its kind, vehicle, rider, bay, fault class, the day it was raised, where it stands and how long it took if it is finished. The page holds no figure of its own. MCF holds one window, one set of case kinds and one bay, declared once as FILTERS.mc.

The window and the kinds are marked mode:true. They are the page’s scope and they are stated permanently in the page head, so counting them left the Filters badge reading 3 on a page nobody had filtered. The bay is the one real narrowing, and it is the one the badge and the chips describe. A period boundary is a day, so mcWindowLbl uses rprBucket, not rprDay — rprDay would mint a working-hours clock the period never had. The date defs carry dayOnly so the filter chip reads the same way.

Every section is on by default: a section is switched off because this month does not need it, not because it has to be found and switched on. A section switched off is absent from the report below and from the export — a preview that differs from the artefact is worse than no preview.

The period is the four-card KPI band: raised, the two halves it splits into, and how much is finished. Open and time to resolve are not dropped, they are read where they belong — the lead line and the time section. Six cards left two orphans on a second row of a four-column band.

Bay against road draws each half as a .hband over its .hstats legend, because status is a mix rather than a single figure and the band always sums to the half.

What is failing is a Pareto per half over the classified cases only, with the unclassified count travelling beside it rather than folded in: a fault ranked against a denominator that includes cases nobody classified is ranked against the wrong number. The tail collapses to one row — eight further faults at one case each is one fact about the period, not eight rows of noise above the faults that repeat. A bar opens the cases behind it, and from there an investigation. Under the two halves, Systems behind the faults groups the same rows back into the thing that is actually failing, which is the question a list of fault names only answers once.

How long it takes leads with time to resolve, and that measure is the bay clock. The PRD defines it as “Median hours, in-bay confirmed → Resolved · the clock starts when Is the tricycle in the bay? is set to yes”, so it runs from the vehicle reaching a bay (bayH), not from the rider’s report.

This first shipped the other way round, reading dur, which starts at the report. That span always includes the rider’s travel to the bay, so every figure was inflated and the verdict drawn from it was wrong — and it was being scored against a target defined on the other measure. The report-to-resolved span is kept as mcReportTtr and shown as one line, because the gap between the two is the wait before the keke arrived, which is real and separately actionable. It is never the headline and never carries a target.

A case that never reached a bay has no bay clock at all, and it is null rather than zero, because zero would read as instant. The measure is a spread rather than an average — the median, the 90th percentile and the mean, with the copy saying which to read, since the tail drags the mean upward — and it is broken out per bay, because one average across four bays gives a head of maintenance the number without the bay to go to.

There is no target on this page. The PM’s health bands are real but not usable here: they live in localStorage per browser and per device, so a Head of Maintenance who has never opened the PM console in that browser is measured against a shipped default while the card says the PM set it. Until Targets ships, the mean is read against a flat MC_REF_H of 24 hours, with a count of how many settled cases came in under it. Targets stays the one surface that scores against bands.

Are we keeping up is raised against settled, day by day, as a dualChart. The gap between the two lines is the backlog.

Vehicles coming back answers the second of two different questions. How many vehicles return is one number; how much case volume those returns generate is another, and the console used to report only the first. The table is deliberately not scoped by kind — a vehicle that came in for a repair and went out again on a field call is the same vehicle in trouble, and narrowing to one kind is what hides that. A vehicle carrying more than one fault class is badged, because that is a vehicle failing across systems rather than a failed repair of the same thing. Where nothing was classified the cell says none classified, not a dash: nothing is missing, the cases were closed without a class.

Parts drawn corroborates the fault ranking independently of how any case was classified. A part is one line on the shelf, so three bearing sizes are three parts — collapsing them is what makes a most-drawn figure wrong. The catalogue used to carry a units total and no date, which made this the one section the period could not reach: change the window and every other figure moved while this one sat still. A draw is now one dated event (MC_DRAW) against a real bay work order, so it narrows with the window exactly like the rest of the page. Every section on the page is a function of the scoped set; none reads the catalogue directly.

Below the computed sections sit Shipped this cycle, Operational improvements and Next. Those are the parts no computation can supply: the figures say what happened, the commentary says what was done about it and what comes next. Each entry is a heading and a line. Edit and Remove both ride on the row, in a .sc-actions pair — removing a line used to mean opening the dialog to read the text you already wanted gone.

There is no sign-off. Nothing here is approved, so nothing is frozen, and a draft badge over a page that never leaves draft states nothing. Build report is the one action: the report is already on screen and this is what turns it into the document. A separate Export button beside it offered the same thing twice. Building records the period, who built it and how many sections it carried, so a period can be found again.

A fault name is a sentence rather than a label, so .hbar--wrap lets the row’s label wrap instead of ellipsing; it shrinks with the card so the paired Pareto cards still fit when the content column is narrow. .pg-head gained flex-wrap — a head carrying a title, three badges and three buttons silently overflowed below about 1000px without it.

Lint: 0 errors. flow-diff: 0 gaps. Runtime probe: 22 view-themes, 0 failures.

Every bar on this page names a set, and most of them used to be dead ends: the reader could see that six repairs took over four days and had no way to ask which six. A time band, a bay, a system and a part each open the cases or draws behind them. The two folded tail rows stay inert, because a row that means “everything else” has nothing specific to show.

A period figure alone answers nothing. Two references sit on the page:

  • The window before this one, the same length. mcPrevScope recomputes the band over it and each card carries the move. The chip’s colour means good and bad in this flow (.trend-up is green), not up and down, so the direction of travel is carried by the words: “23.2% more” against a red chip on cases raised, against a green one on cases settled. The store therefore holds the prior fortnight as well as the reported one, because a comparison needs something to compare with.
  • The PM’s own target. PMTGT already carries the band for median time to resolve, set on the Targets page. The card scores this period’s median against it with tgStatus, rather than printing a median and leaving the reader to remember what it was supposed to be.

Vehicles coming back carries what every other table on this console carries: search, a last-kind facet, a minimum-cases facet, a clear, an export, a pager and sortable columns. It showed ten of seventy-seven with no route to the other sixty-seven. Clicking the column already sorted reverses it, which is the only way to ask which vehicle came back least recently without a second control.

The Sections card says the analysis below is exactly what exports, and that was a claim about something that did not happen. repBuild now prints, under @media print rules that drop the shell, the filters, the contents card and the history, so the artefact and the preview are one thing rather than two claims about each other.

The contents navigates, and the choice survives

Section titled “The contents navigates, and the choice survives”

Seven sections over twenty thousand pixels need more than a list of names, so each switched-on title jumps to its section. The set of sections a reader chose is kept in localStorage and restored on the next visit, falling back to all seven where storage is unavailable. A switched-off title reads as inert rather than offering a jump to something that is not rendered.

Order is what a reader scans, so each entry moves up and down within its own group. Entries never cross between groups: the three groups are separate sections of the document.

The work-order record’s Evidence table read “Tier-1 capture · 1 photo” and showed nothing, which is the one thing a capture row must not do. The count moved into the card head where a count belongs, and the photograph itself is on the record, using the .evi / .evi-thumb atoms cluster-manager already ships and the shared prototypes/assets/photos store. A thumbnail opens the full frame. EVID keys captures to work orders rather than picking at random, so a record shows the same photograph every time it is opened. The battery record carries the same block against the pack.

.pstat-row was four fixed columns, which overflowed every page on this console below about 1200px and the Command Centre worst of all. Two columns there, one below 640px.

The store can ask for an item the catalogue does not carry

Section titled “The store can ask for an item the catalogue does not carry”

Asking for a part the catalogue did not hold was refused outright — the store was told the part is not one Inventory fulfils from, and that was the end of it, so the only way to get a new part listed was outside the app. It is now a new item request, and it travels the same road as any other.

A request carrying a new item, or an item Inventory has flagged, waits at Pending approval before the warehouse sees it. The approval rung is set by the PM Tricycle Hardware and the approver is the raiser’s line manager: the store raises here as the Head of Maintenance, so it goes a step up rather than back to the raiser, and lands on the VP’s Item approvals desk.

A request waiting on approval has not reached Inventory, and the card says so. “Stock coming from the Inventory Manager and going back · 8 open · 1 not sent yet, waiting on approval” counts the two apart, because counting a request the warehouse has never been given as work it holds is the screen asserting what the data does not support. The row names who it waits on.

Bay replenishment is a different line. A bay raises it and the Head of Maintenance is the line manager for that one; it is unchanged, and the two sit on the same console without being the same thing.

Asking for a part the catalogue does not carry says what it is

Section titled “Asking for a part the catalogue does not carry says what it is”

The composer already took a free name and turned anything the catalogue did not hold into a new item request that goes up for approval. None of that was visible: the field said “Search the catalog”, the hint went blank on an unknown name, and nothing said the store could ask for something that does not exist yet — so the only way to get a part listed looked like it was outside the app.

The line now says which of the three it is as the name is typed: held in the store, on the catalogue but flagged by Inventory, so it waits on the line manager, or not on the catalogue at all, so it goes up as a new item request. Each one names where it goes.

The flagged list is Inventory’s and matches it item for item, and the queue carries a live example: a request for four inverters waiting on the VP, beside the new item request already there.

Asking the warehouse for stock it holds and asking the estate to start stocking something new are different asks with different fields, and one field was doing both: the part box searched the catalogue and also, silently, accepted anything typed into it as a new item request.

The part box does one job now and says so — Searches the catalogue Inventory fulfils from — and the other ask has its own door. Not in the catalogue? opens the item composer the Production and Retrofit Station Managers already use: item name, category, unit, quantity, needed by, urgency, what fails today, the industry standard or part number, and a photograph. Nobody can source a part from a name and a quantity, so the composer will not send without the standard.

A flagged item on a bay request takes both approvals

Section titled “A flagged item on a bay request takes both approvals”

Bay replenishment is approved by the Head of Maintenance, and that is unchanged. But the flag Inventory sets is about the item, not about who asked, so a bay request naming a flagged part takes this desk’s approval and the line manager’s. Approving it here no longer sends it to the warehouse; it sends it one rung up, and the record names both rungs — who signed here and when, who it is waiting on now, and which part put it on the second rung.

A request waiting on a decision is still the raiser s to change or stop

Section titled “A request waiting on a decision is still the raiser s to change or stop”

Nothing has moved and nothing is committed while a request waits, so the quantity on each line is an editable figure and Void request sits beside it. Voiding takes a reason, which is what the approver and the store both read later, and the request stays on the list as Voided rather than disappearing. Once it is decided the record is read-only, because by then it is a commitment somebody else made.

The same thing cannot be asked for twice. A name already on the catalogue is a stock request, not a new item request, and a name already waiting on a decision is the same request twice. Both are refused at send and named: already requested as HSR-0146, waiting on VP Tricycle Hardware.

The case record carries what the live case holds

Section titled “The case record carries what the live case holds”

The record named the fault, the stage and the bay, and left out most of what somebody opening a case actually reads. It now carries the whole of it, in five cards.

The case is the intake as well as the fault: where the case came from, whether the keke is at the bay and how long it has been there, the complaint in the rider’s own words, the primary and the secondary fault, who raised it and when, and when it last moved. A case nobody has triaged says so on every row that depends on triage, rather than showing a blank.

Rider and vehicle identifies the thing in front of the technician: the rider and their phone, the plate, the chassis number, the VCU and the battery serial fitted now, the kit revision, the odometer at the last swap and the cohort.

This vehicle’s record is every earlier record on the plate, this case excluded: how many, how many still open and which, the last repair and where, the fault that repeats, how many the bay flagged unsafe, how many were raised at P1, and the bays involved. It counts the rows Vehicle history lists, because both are built from one list: the card pointed at that tab and used to count a different population, which is worse than carrying no card.

Last known position is where the vehicle last reported from and when, off its VCU. Assignment names the technician, who triaged it and the department the case sits with.

Every row that can be derived is derived, so the record and the tables cannot disagree. Only what the console has no source for is seeded: the VCU and battery fitted, the last GPS fix, and which earlier repairs a bay marked unsafe.

The store can see where its request has got to

Section titled “The store can see where its request has got to”

A request left the store and the next thing it showed was an outcome. The record now draws the line it travels, the same one the floor managers read: who asked, the line manager, Inventory, Procurement, and back to the store.

The two kinds take different roads and the line draws the one taken. An item the catalogue already holds needs no catalogue set-up and nothing bought if the warehouse holds it, so it reads requested, approved where Inventory has flagged the item, fulfilled, received. A new item cannot be bought before it exists, so Inventory sets it up in the catalogue sits between the approval and Procurement. A declined or voided request stops where it stopped, rather than drawing a road it will never travel.

A replaced VCU is reassigned on the case, into one register

Section titled “A replaced VCU is reassigned on the case, into one register”

A control unit gets swapped at a bay and the vehicle record went on naming the one that came out, so every surface reading the VCU was reading a unit that is no longer fitted.

Reassign sits on the VCU row of the case. It takes the serial of the unit now on the vehicle and why the old one came off, and refuses three things: a serial that is not a VCU serial, the unit already fitted, and a unit the register says is on another vehicle, because one cannot be on two.

The assignment is one register. The retrofit station writes to it when a VCU is fitted on the line and when it swaps one; a bay writes to it when a unit is replaced in repair. The VCU on the case is read off the latest entry rather than stored beside it, which is what stops the two drifting apart, and the row says when it was fitted, by whom, and from which side.

Saving writes the change to the case audit trail, old serial to new with the reason, so the swap reads on the timeline beside everything else that happened in the repair.

Tricycle cases and the work order page describe the same keke

Section titled “Tricycle cases and the work order page describe the same keke”

The case opened from Tricycle cases and the work order opened from a bay are two pages, and only the second had been brought up to what the record holds. Both are now built from the same three helpers, the rider and the vehicle, the vehicle’s earlier record, and where it last reported, so neither can describe the same machine differently. The case page carries the five cards and reads Overview, Notes and Timeline, Evidence & parts, Vehicle history.

Every vehicle on a case shows a fitted record. Four plates had a hand-written one and twenty-one appear on cases, so most cases opened on a row of dashes. Writing twenty-one more by hand goes stale the moment a seed moves, so the rest are derived from the plate: the same plate always gives the same VCU, battery and kit revision, a hand-written fixture still wins over the derivation, and a reassignment written at a bay wins over both.

Three things the case page stated that were not true. The origin read the triage state, so an untriaged case claimed it had come from “Awaiting triage”; origin is where the case came from, a bay walk-in, a scheduled service or a field escalation. A keke at the bay still carried the note that nobody had answered whether it was there. And the department line read “a unclassified”.

The unit offered is the one this repair fitted

Section titled “The unit offered is the one this repair fitted”

A control unit reaches a bay through the bay’s own inventory and its serial is captured when the bay accepts it, so by the time it goes on a keke the system already knows the unit. What the system does not know is that the keke now runs on it: fitting a part during a repair is not an assignment, which is why the Head of Maintenance has to reassign it deliberately.

So Reassign offers the control unit used on this work order and nothing else. A shelf list would let a unit be assigned that this repair never touched, and a text box would let one be assigned that nobody has received. Where a work order fitted no control unit the action is not offered at all, and the dialog says why.

The unit that comes off is held for assessment rather than returned as fittable: a bay pulling a VCU has not judged it yet.

Every unit the vehicle has run on, and when each came off

Section titled “Every unit the vehicle has run on, and when each came off”

The old serial was on the case audit trail, which is the right place for what happened during a repair and the wrong place for the vehicle’s identity. Six months on, somebody asking which unit a keke was running in June is reading a repair rather than a register.

VCU history reads the register out on Vehicle history, beside the rest of the vehicle’s record: every unit, when it went on, who fitted it, from which side, and when it came off, which is simply when the next one went on, with the reason it was changed. The current unit’s row on the record says how many earlier ones there are, so the history is findable from the thing that prompts the question.

The VCU row reads in one line, and the first page shows one

Section titled “The VCU row reads in one line, and the first page shows one”

The row carried four facts under the serial, the day, the clock, the person and where to find the rest, which wrapped onto two lines and read as a sentence rather than a record. The clock and the person are in VCU history, which is where somebody goes for them. The row keeps the two that answer a glance: Fitted 30 Jul 2026 · Maintenance · 2 on record.

The seeds that demonstrate it sit on the first page. One plate out of twenty-one had a replaced unit and it was four rows down, so opening the top of Tricycle cases showed a vehicle that had only ever run on one. The first four rows now carry a VCU record and two of them carry a replacement, one done at the retrofit station and one at a bay, so both sides of the register are visible without performing a swap first.

A scheduled work order could not be opened at all

Section titled “A scheduled work order could not be opened at all”

Four work orders carry the schedule they came off, and the record read that schedule four lines before anything declared it, so opening any of them threw and the page stayed empty. Three of the four sit on the first page of Tricycle cases. It was never a layout fault, which is why nothing caught it: the probe walks views, and this only fires on a row click.

The store composes a catalogue name, because the approval mints the item

Section titled “The store composes a catalogue name, because the approval mints the item”

A free text box cannot produce a catalogue record, and the approval has to mint one, so the store has the same composer the floor managers use: the item type off the controlled list, the descriptor, the key specs, and a version where two items are otherwise identical. The name assembles in front of the person raising it, and will not send where it matches something already on the catalogue or something already waiting on a decision. A near match of either warns without blocking, because two items can be close and still be two items.

Lifted from the production console rather than rewritten, so a name means the same thing wherever it was raised.

Parts fitted are recorded here, from the bay that did the job

Section titled “Parts fitted are recorded here, from the bay that did the job”

The Evidence & parts tab printed what was fitted and offered nothing to do about it, so a part fitted after triage, or swapped on the bench, never reached the record. It carries Add a part, Change serial and Remove.

THE TABLE IS ARGO’S WorkOrderPart, not a shape of our own:

WorkOrderPart { itemUid, itemName, itemSku, newSerial, oldSerial,
supplierBatch, note }

and the model’s own comment says what a row is: a part swapped during a repair, linking the work order to the broken (old) and replacement (new) serials. So the columns are Part, Fitted, Came off. There is no status, and the Requested / Accepted / Mismatch / Pushed words the seed carried are gone with it.

Adding a serialised part names the unit. The dialog lists the serials standing at that bay and the technician picks the one that came off the shelf, rather than the console taking the first and being wrong. A part counted in pieces has no unit to name and shows a quantity instead, capped at what the bay holds.

That makes the swap native rather than bolted on. Changing the fitted serial is just filling oldSerial and newSerial: the unit that was on goes back to the bay’s shelf, the chosen one comes off it, and the on-hand count does not move because one went on and one came off. Removing leaves oldSerial set and newSerial empty, and the count goes up by one.

Came off is a serialised column, and it is filled by the act rather than typed. A part counted in pieces has no unit to name, so both serial cells read — and the row carries a quantity instead. For a serialised one the swap itself supplies it: Change serial moves the outgoing unit into Came off, Remove puts the unit that left there. A first fit leaves it empty, because the vehicle record holds the VCU (VCU_LOG, vcuNow) and the battery pack (VEH_FIT) and no other fitted serial, so for a motor or a controller there is nothing on the vehicle to read the outgoing unit from. Where the vehicle does carry the slot, the outgoing serial belongs to the vehicle and not to whoever is typing.

What can be fitted is what the bay holds. The picker is bayStock(w.bay), the same list the bay’s own Stock panel shows, with on-hand on every option, so a work order cannot record a part that was never at the bay that did the job. A serialised part takes a specific unit off HOM_BAY_SER; Change serial corrects it if the wrong one was taken. A part counted in pieces has no serial to change, so that row does not offer it: one fuse is any fuse.

Off the vehicle is back on the shelf, with nothing to ask. There is no quarantine fork here. The row is marked off rather than inferred from an empty serial pair, because a part counted in pieces has no serial either way and the row would otherwise look untouched.

A quantity the bay cannot cover is refused, not quietly reduced. Asking for twenty where there are eighteen says so and leaves the dialog open; being given eighteen without being told is an answer to a question nobody asked.

The tab the reader was on survives an action. The detail re-renders its whole strip and segPanels always marks the first tab active, so fitting a part from Evidence & parts used to drop them back on Overview.

A part counted in pieces carries its quantity. A serialised row is always one unit; a row counted in pieces carries its count and is corrected by Change quantity, which moves the shelf by the difference rather than by the whole.

The tile and the table read one list (woFitted). The headline counted the derivation while the table counted the live list, so fitting a part moved the table and left the figure at its seeded number.

woFitted is deliberately not called partsFor: this file already has a partsFor(name) returning a part’s replacement history, and the later definition wins, so the first name silently resolved to the wrong function.

The console is Atlas. Its wordmark read Argo, which is the mobile app this page reports on: the work-order ids, the triage choices and the notifications here are references to it, not a brand for this surface.

Parts on hand says what the warehouse has, not just what maintenance has

Section titled “Parts on hand says what the warehouse has, not just what maintenance has”

The table answered what the store and the bays hold and stopped there, which is half the question a Head of Maintenance asks: the other half is whether a replenishment request can be met today.

At the warehouse is that figure (INV_ONHAND), and it is a number: none reads 0, not a sentence. A column of quantities is scanned, and a word in the middle of it stops the eye on the one row that has nothing to give.

In the store and At the bays fold under On hand to make room, because the table was already at eight columns and the split is a reading of the total rather than two separate measures. The totals row still carries both, and Where it is names the bays by name beside their quantities.

A block is automatic: a part the bay cannot supply raises one, and the arrival of that part clears it. That covers the one wait the console can see for itself. A bay waiting on a battery, on R&D or on Telematics had no way to say so, and the resolve-clock kept running on a job nobody could move.

The reason is a list, not a sentence (BLOCK_REASONS / WO_BLOCK_REASONS): Waiting for battery, Waiting for R&D, Waiting for Telematics. The same three words in both consoles, because one block read in two places must not have two vocabularies. A typed reason cannot be counted, and the first question anyone asks of a blocked queue is what it is blocked on.

Escalated to is a person, and picking one sends them the case. The desk the reason belongs to leads the list, since that is who it is almost always for. The mail is the record consolidated into one brief, the overview, the notes and timeline, the vehicle history and the parts, with a subject that says it is a blocker, so the reader does not open the console to find out what is being asked of them. Choosing nobody blocks the job and sends nothing.

A manual block is cleared by hand, with a reason. Nothing arrives to clear “waiting for R&D”, so all three unblock the way the console already unblocks. The automatic parts block keeps clearing itself.

A block carries when. The stamp is date and minute, not “today”, because the resolve-clock is paused from that moment and a week later “today” means nothing. It rides the banner the automatic parts block already uses, so a manual block and a stock block read the same way: Blocked · Waiting for battery · escalated to Adaeze Nwosu · since 25 Oct 2026 · 16:00. Resolve-clock paused.

A finished job cannot be blocked. Awaiting collection and picked up are not waiting on a battery, so the control only exists while the work is running.

A keke still inside its first three months

Section titled “A keke still inside its first three months”

The same fault reads differently on a keke converted five weeks ago, so the case says which it is: a red Converted N days ago badge on the .pg-title-row, beside the status, on the case and on the tricycle work order both. The battery work order’s head carries a pack and a model rather than a plate, so it gets none. It stops at three months, because past that the conversion is no longer the first thing to suspect and a badge on every case would say nothing at all. The Conversion station manager counts the same window from the other end, as its 3-month breakdown rate.

A home bay was a value stored on each keke, with no surface that set it and no rule behind it. The key is now the mesh, a .pseg tab beside the bay register.

The cluster cannot choose a bay. It is an operations and pricing unit: Sango Ota holds 1,026 of 1,084 active riders and three of the four bays, up to 15.6 km apart, so a cluster rule gives 95% of riders the same answer and decides nothing. A mesh is one to four swap stations, and riders already behave as though it were their home bay — Honda Mesh takes 95% of its triages at Honda, Ifo Mesh 84% at IFO. The three smaller clusters have 58 riders between them and no bay at all.

There is no held state, because nothing is held. The first cut had one: a mapping to an under-staffed bay saved as Held, and riders stayed on their current bay until it was staffed. That invented a word the console already had and asserted a mechanism nothing implements. The mapping is the mapping; the row reports Bay under-staffed · 1 of 3 in the same words the bay record uses, and the dialog says to staff the bay before saving rather than pretending to withhold the change. Under-staffed is bayRoster(id).length < bay.min, read from the roster, not a second flag.

A home bay is not a triage bay. The first forecast summed the triages of every mesh mapped to a bay, as though homing decided where cases get triaged. It does not: homing says where a keke belongs for scheduled work and where the send sheet opens, and the console has always kept the two apart in its Home bay and Serviced at columns, which read away from home when they differ. The only honest bridge is what a mesh has historically taken at one bay — Honda Mesh 95%, Alagbado 92%, Ifo 84%, Metro 68% — so the forecast reads what the bay actually records, adds the share of this one mesh’s unmet demand expected to land there, and says what would still be triaged elsewhere. The first version read 284 a month for Joke Ayo; the honest figure is 179, because the first double-counted Metro and assumed every triage lands at the home bay.

Cluster bay fallbacks are a register, because clusters keep arriving. They began as a modal with one select per cluster, which works at four and fails at thirty: the body scrolls, and reaching the one cluster that matters means passing eight that say No bay in reach. Worse, a cluster added tomorrow arrived with no entry at all, its riders fell through to no bay, and the only sign was a counter on a button. It is now a third tab beside Bays and Home bays — a tab rather than a second table under the mapping, which would have stacked two table cards on one page.

Not set is a state of its own. An absent entry means nobody has decided; an empty one means someone decided there is no bay within reach. The old model held both as an empty string and so could not tell them apart, which is exactly why a new cluster was silent. Now it arrives as an amber Not set row and raises a line above the table naming it and the riders it would strand, and choosing No bay in reach is a decision that clears it.

The riders who reach this rung are the only ones counted. A fallback is read only by a rider with no mesh, which 119 active riders are, so the column is Without a mesh rather than the cluster’s whole roll. It is spread across the clusters in proportion to what each holds, because the console has no per-cluster figure for it and a seeded one would be a number nobody could reproduce.

One fallback per cluster, not one for all of them. The default was a single bay, which sent an Ikorodu rider to a Sango Ota bay — exactly the error that keying on the mesh exists to avoid. There are four clusters and they are not alike: Sango Ota holds 1,026 riders and three bays, while Mowe, Ikeja and Ikorodu hold 58 between them. Each sets its own, and No bay in reach is a real answer for a cluster that has none, rather than a bay far enough away to be fiction. The cluster is held against the rider rather than read off where they swap, which is what lets it answer at all when no mesh can.

The forecast reads as arithmetic, because that is what it is. Its lines were takes today, would bring and would take — two near-synonyms either side of a number, so the sum between them was invisible, and on a bay handling nothing the last two showed the same figure and looked like one fact said twice. They are now handles now, would add and would then handle: 309 + 43 = 352, on screen.

The staffing warning is in the picker, not under it. The dialog carried a roster row and a three-line notice repeating what the State column already says. Both are gone: every under-staffed bay is marked in the select itself, so the consequence is read while choosing rather than argued underneath.

The dialog shows what the bay handles, and stops. It grew a projection — what this mesh would add, and what the bay would then carry — which stacked two estimates on each other: one mesh’s conversion applied to another’s volume, then the share that mesh takes at a single bay. A manager choosing a bay needs its load; whether it can take more is their judgment, not a number to be derived for them. Everything that existed only to compute that projection is deleted, down to the cases and atHome fields on the mesh seed.

On average means across months. July is partial in this data, the first triage landing on the sixth, so it is held for the record and never averaged — a short month drags the figure down for a reason that is not about the bay. Honda reads 335 a month from August, September and October, not 275 from all four. A bay with nothing recorded says nothing yet once, rather than a zero average beside a working-day rate of zero beside three zero months.

One fallback per cluster, not one for all of them. The default was a single bay, which sent an Ikorodu rider to a Sango Ota bay — exactly the error that keying on the mesh exists to avoid. There are four clusters and they are not alike: Sango Ota holds 1,026 riders and three bays, while Mowe, Ikeja and Ikorodu hold 58 between them. Each sets its own, and No bay in reach is a real answer for a cluster that has none, rather than a bay far enough away to be fiction. The cluster is held against the rider rather than read off where they swap, which is what lets it answer at all when no mesh can.

The forecast reads as arithmetic, because that is what it is. Its lines were takes today, would bring and would take — two near-synonyms either side of a number, so the sum between them was invisible, and on a bay handling nothing the last two showed the same figure and looked like one fact said twice. They are now handles now, would add and would then handle: 309 + 43 = 352, on screen.

The staffing warning is in the picker, not under it. The dialog carried a roster row and a three-line notice repeating what the State column already says. Both are gone: every under-staffed bay is marked in the select itself, so the consequence is read while choosing rather than argued underneath, and the rider override dialog had always done it that way.

No rate is shown, because this one cannot be read on a single mesh. Its denominator is every Selene issue type a mesh’s riders raised, maintenance or not, so the level is understated and only the comparison between meshes carries. On a maintenance console 4.9% of its cases reads as maintenance cases, which it is not — and the raw counts beside each other were no safer, since a reader can divide one by the other and arrive at the same figure. The dialog states triages and riders only. The estimate behind would add still rests on that ratio, but it is applied to one mesh’s volume on the same basis it was measured on, and it reaches the page as a count of bay visits rather than a rate to be read.

The forecast names the gap, because the gap is the decision. It first read its unmet cases at Honda Mesh’s rate, times the 95% it takes at one bay, which asked a reader to hold three ideas to get one number — and on Honda Mesh itself measured a mesh against itself, returned nothing, and explained the nothing with its own name. It now states what the mesh reaches today, then what is recoverable, and a mesh already at the ceiling reads nothing · nothing more to recover rather than a formula resolving to zero. The benchmark it was measured against is not shown: it is a second number to read for a decision it does not change.

The forecast is struck on demand, not on today’s triages. A mesh moved closer to a bay does not bring the work it already manages, it brings the cases it has been failing to deliver: Alagbado raises 723 cases in 30 days and lands 13 at a bay. So the dialog shows what the receiving bay would carry at the best rate any mesh achieves, names that mesh rather than quoting a bare percentage, and names every mesh behind the figure — the first version labelled the total With Alagbado Mesh when it already included Metro.

Cases reaching a bay is not a service level. It is triages over every Selene case a mesh’s riders raised, maintenance or not, so the absolute level is understated and only the comparison between meshes carries. The card says so, to stop 21% being read as one maintenance case in five.

Siting the bay is ours; siting the station is not. Distance needs two positions and the console held neither — a bay carried a human-readable address and no coordinates. The bay now takes a latitude and longitude where its other details are edited, and half a coordinate is refused, since saving one would site the bay in the sea. The swap station’s position belongs to the swap team and is read rather than written: three of theirs are wrong, Redeem Camp plotting near Lagos Island, so those meshes read Redeem Camp not sited and the fix is raised with the team that owns it. An unsited station is checked before the mapping, because it is why no bay could be chosen, and reporting no bay there would hide the thing that needs doing.

homeBay(plate) read a value stored on the keke. It now resolves through four rungs at read time: a rider override, the mesh the rider’s primary swap station belongs to, the cluster default, then nothing. Resolving rather than copying is what makes a station move or a remap apply at once instead of leaving every record stale — and it is why the send sheet, the occurrence tables and the schedule rows all follow a remap without being touched.

The fleet was already full of overrides. Every keke carried a bay someone had set by hand, which in these terms is rung one, so it is read as one rather than discarded. That keeps every page that scopes work by bay reading what it read before, and it makes the mapping visible the moment a keke is handed back to it: clear LND-238-KJA and it goes from Set for this rider · Honda Bay to From Metro Mesh · Joke Ayo Bay.

The rung comes back with the answer. A bay nobody can explain is a bay nobody will trust, so the record says which rule gave it: Set for this rider, From Metro Mesh, Metro Mesh has no bay · cluster default. An exception is only legible if the record says it is one.

An override escapes the staffing check, and says so. The mapping refuses to send a mesh to an under-staffed bay, but one rider is the manager’s call, so the dialog states the roster against the minimum and saves anyway. Guarding a single keke the way a 273-rider mesh is guarded would be the console overruling the person using it.

Handing a keke back is not the same as having no bay. Clearing removes the override so the mapping answers, rather than blanking the field, which would read as a keke with nowhere to go.

33. A data table’s row is the target, or it says it is not

Section titled “33. A data table’s row is the target, or it says it is not”

The rider record’s Vehicle history and Parts replaced tables could only be opened by their trailing control, which is the defect the canon names: the row is the target. Nothing caught it, because probe-ci reaches a page through the sidebar and this one opens on a row click, so CI had never measured it.

The fix is in dtRender, which nine tables share. A row carries its own target as goTo — a row array is an object, so none of the nine call sites changed shape — and the table declares allow-static-rows when no row in it has one. That second half matters as much as the first: most of those nine are genuinely static, a movement-history line is not a record to open, and the canon’s own opt-out is how a table says so rather than failing the check in silence.

The target follows the record, not the table. A prior entry on this vehicle opens as a repair, a case or a battery case depending on which register it came from, and a replaced part opens the work order that fitted it — openWO for a repair, openBat for a battery one. Each was driven to confirm it lands on pg-repair, pg-case or pg-battery rather than merely not throwing, which is the rule the Screens canvas holds for the same reason.

Taxonomy Governance carries a third tab, Field rescue cases, beside Proposed modes and Reference catalog.

The catalog already on this page is an engineering one: system, component, failure mode, assigned after a teardown. The rescue taxonomy is what a caller said down a phone and what a technician found standing beside the keke. They describe the same vehicles and they are not the same vocabulary, so merging them would let a case be filed under a mode nobody diagnosed — which is the failure the two-layer split exists to prevent.

They sit side by side because a case escalated from field support arrives here, and the Head of Maintenance has to be able to read the code it came in under.

The tab reads from /components/field-support-taxonomy.js, the file both field support apps read. It is not copied: a second declaration here would let this page show a level the app that raised the case never applied.

Governance is not on this page. The Field Support lead promotes and merges the rescue list; this page governs the maintenance catalog. So the tab says who owns it rather than offering buttons that would be a second, competing authority over one list — and the v2.3 · append-only badge, which versions the maintenance catalog, stands down on this tab rather than stating a version this page neither sets nor owns.

A filter in the card head switches between Reported symptoms and Technician findings. The two halves do not share columns — a technician’s finding has no caller and no priority — so the header and the <colgroup> are rebuilt with the rows rather than padded out with dashes, which would assert a blank the data does not have.