Facilities persona buildout — batch plan
Facilities persona buildout
Section titled “Facilities persona buildout”Closing the loose ends and dependencies across the Facilities persona family, then finishing the personas whose surfaces are still stubs. Written mid-programme so a fresh session can pick up batch 3 without re-auditing.
Audit findings (28 Jul 2026)
Section titled “Audit findings (28 Jul 2026)”Three of six facilities personas were complete; three had sidebar items that all
landed on a pg-soon placeholder.
| Persona | State at audit |
|---|---|
| Facilities PM | 7 surfaces, complete |
| Regional Facility Manager | 8 surfaces, complete — the build reference |
| Estate Manager | 4 surfaces, complete |
| VP of Facilities | Overview only · 5 stubs |
| Facility Manager | Overview + Facilities + record · 5 stubs |
| Station Manager | Dashboard only · 5 stubs — out of scope, see below |
The headline dependency was not a missing screen but an unimplementable spec. Estate Manager’s PRD assigns two decisions to the PM — FR-8 (accepting a location creates a Project) and FR-11 (only PM/VP advance a stage) — and the PM flow had nowhere to perform either. Estate Manager is built read-only on stage because the PM owns it, so the lifecycle was closed at both ends. In 2,119 lines facilities-pm mentioned “Expansion” once and “lease” twice.
Decisions
Section titled “Decisions”- VP monitors only. FR-11 names the VP alongside the PM, but the VP surface is
a monitoring view: advancement stays PM-only, on facilities-pm’s project details
page. Enforced structurally — no
.pg-actions, no row click, no advance function in the file. - Station Manager is out of facilities. The remit stays; the flow will belong to
a different department. Do not delete it and do not build its surfaces here.
Which department is still open, so
products.ts(category: 'facilities') and the README row are deliberately untouched. - Facility Manager becomes the cluster-tier origin for facilities-internal issues, now that Station Manager is leaving. A Station Manager in another department files into facilities as a Department Requester, which facilities-pm already models (“requests from clusters and departments”, plus a Department Requester persona in its PRD §12). The chain still closes.
Batches
Section titled “Batches”| Batch | Scope | State |
|---|---|---|
| 1 | PM Projects (FR-8 accept, FR-11 advance) + VP Expansion (monitor only) | Done — fd9dd34, b249618 on atlas/facilities-projects-pm-vp |
| Dropped — different department | ||
| 3 | Facility Manager — issues, requests, equipment, contractors, sops | Done — see “Batch 3 outcome” |
| 4 | VP — regions, facilities, issues, reports (+ 2 review findings, below) | Done — see “Batch 4 outcome” |
Batch 3 — Facility Manager
Section titled “Batch 3 — Facility Manager”products/atlas/flows/facilities-manager/index.html, 914 lines. Cluster admin
(Ogun). Five navs land on pg-soon: issues, requests, equipment,
contractors, sops.
Mirror facilities-regional-manager, not facilities-pm. The
Regional Manager is the same shape one tier up (region vs cluster) and already
carries every dataset this needs: ISSUES, REQUESTS, EQUIPMENT, CONTRACTORS,
SOPS, FACILITIES, SEV, STAT_ISS, STAT_REQ, EQ_COND, ACK_ST, IC_*.
facilities-pm is Super Admin and scopes wider than a cluster admin should.
Scope per the flows README: cluster health, issues, requests, equipment, contractors; SOPs read-only; requests forwarded to the PM.
What this flow already has: .pstat, .tbl-card, .pseg, .rec-grid,
.card-eyebrow, .empty-wrap, .pager, .filt-select, .filt-search,
.td-title, .td-sub, .dlg-overlay, .feed-row, toast(), rememberPage(),
_PG_TOP. It has no esc(), dwRow(), openModal(), logRow(), .tl-step-*,
.readonly-box or .note-composer — add only what a surface actually needs, copied
verbatim from the Regional Manager.
There is no design.md for this flow yet. Write one in the same commit: the
canon lint reads a flow’s design.md against its sibling index.html, and
CLAUDE.md requires the doc to move with the flow.
Also fix while in this file (from the PR #73 design review)
Section titled “Also fix while in this file (from the PR #73 design review)”Three pre-existing findings the CI design-review raised against this file. They
are unrelated to what #73 changed, but this batch is already rewriting the flow, so
fix them here rather than in a separate pass.
:584— the Facilities directory footer count is dead. The.tbl-foot<span class="lbl">has noid, but:906doesdocument.getElementById('fac-foot'), which returnsnulland is swallowed by anif(f)guard. Filtering hides and shows rows while the count silently never moves. Addid="fac-foot". The Overview table next to it (#att-filter→#att-foot,:537,547,896) is the compliant version to match.:346—.rtis a re-skin of.rec-panel..rt{display:none} .rt.on{display:block}is used at:602,615,626,637,646,655for.psegsub-panels. The canonical.rec-panel/.rec-gridCSS is already defined verbatim in this file at:122-124and never used. Rename every.rt/.rt onto.rec-panel/.rec-panel onand drop the.rtrule.:386-388—.facts/.fact-lbl/.fact-valis a re-skin of.td-row. Used at:604-610for the Facility details field rows. The canonical.td-row/.td-k/.td-ic/.td-vCSS is already in this file at:291-296and never instantiated. Convert to.td-rowrows, which also retires this flow’s share of the deferred.dw-row→.td-rowmigration.
Both re-skins are the exact pattern the canon calls out: a private class doing a canonical component’s job while the real CSS sits unused in the same file.
Batch 3 outcome — decisions that carry into batch 4
Section titled “Batch 3 outcome — decisions that carry into batch 4”All three PR #73 findings fixed, five surfaces plus #pg-issue / #pg-request /
#pg-standard details pages built, design.md written, pg-soon gone. Lint 0 on
both files (explicit paths, 2 files confirmed). Four judgment calls worth reusing:
.dw-rowwas NOT imported;.td-rowwas used instead. The reference’sdiRow()emits.dw-row, but finding 3 retires this flow’s share of that migration — importing it would have re-added what the batch just removed. The flow now instantiates zero.dw-row..dw-status(a 3-line badge-group wrapper) was copied verbatim, because the triage explicitly rules that one fleet-wide: “one flow must not diverge alone”.- Table wrapping is invisible to an overflow check. With
table-layout:fixeda too-narrow column wraps instead of overflowing, soscrollWidth - clientWidthreads 0 while cells are two lines high — batch 1’s measuring advice misses it. Measure instead byRange.getClientRects().length > 1per text node. Five tables wrapped; each got a<colgroup>taking width from its narrow badge/numeric columns, plus a.tbl td{white-space:nowrap}+ ellipsis guard. Batch 4’s finding 1 is the same defect — use the same fix and the same measurement. - The reference’s case-note
<textarea>is unstyled (no.fld, no rule of its own), so it renders as a raw browser field in the wrong font. Copying.note-composerverbatim would have shipped that; the textarea is wrapped in the canon.fldblock here. - Derived, never stored. Site health, its attention reason and every Overview
KPI are computed from
ISSUES/EQUIPMENT/REQUESTS. The old static markup had already drifted (Overview claimed 9 open issues, the directory column summed to 8; Ijoko passed “no high-severity issues” while holding a High one). Batch 4’s roll-ups should derive from the same data rather than restate it.
One known cosmetic consequence: at first load both needs-attention sites derive the same reason (“High-severity issue”), so the reason filter offers one real option until a state change differentiates them. That is the honest output of the data, not a hand-labelled distinction.
The bar moved mid-batch — batch 4 starts here
Section titled “The bar moved mid-batch — batch 4 starts here”uat advanced 26 commits during batch 3 and raised the acceptance bar. Merged
cleanly (only 7fc61ad, the fleet-wide login standardisation, touched this flow,
and it is confined to the login block). What batch 4 must now satisfy:
- CLAUDE.md ritual is six steps, not five. New step 6: load the flow and run
site/scripts/flow-probe.json every view, not just the ones you touched —flowViews().map(v => [v, flowProbe({view: v})]), allpass:true. It catches what no file-reader can: wrapped KPI labels, clippednowrapheaders, rows whose only target is a chevron, ragged text edges. Run it at 1440 — at a default ~730px pane it reports failures that are just the narrow viewport. web-dead-pageris a new staged rule and the hardcoded<button disabled>‹ Prev</button>pager is now a violation over a JS-renderedtbody. facilities-manager had 7 grandfathered; batch 3’s new surfaces pushed it above baseline and turned them into 5 errors. All 12 removed — no surface paginates, so the.tbl-footkeeps only its derived count. facilities-vp still carries its own grandfathered count, so batch 4 will hit this the moment it adds a table.- The probe requires EVERY table row to be clickable (unconditional, once a
table has rows). Purely informational tables fail. Equipment rows now open the
site holding the item on its Equipment tab; a contractor is too thin for a
details page, so its row opens a compact centred
.dlg. Batch 4’s read-only roll-ups need a target per row — decide it up front rather than retrofitting. - Ratchet with an explicit path, never
--all.--update-baselineonly rewrites entries for files it actually linted, solint-canon.mjs <file> --update-baselinegraduated this flow to zero-tolerance and left the other 24 backlog entries alone.--alllints zero files on Windows and reports success, which here would have looked like a clean wipe.
Batch 4 — VP of Facilities
Section titled “Batch 4 — VP of Facilities”products/atlas/flows/facilities-vp/index.html. Overview and Expansion are built;
regions, facilities, issues and reports still land on pg-soon. Read-only
executive roll-ups. Depends on batch 3 so the issue/request model is settled first.
Follow this flow’s own idioms, not facilities-pm’s: no esc() (trusted literals), a
function-local set(), .r for right-aligned cells, and the empty state as a
colspan row inside the tbody.
Also fix while in this file (from the PR #73 design review)
Section titled “Also fix while in this file (from the PR #73 design review)”:181+ row template:850— the “Reason” column wraps to two lines in every region’s Sites needing attention table (.tbl tdhas no nowrap/ellipsis, and no<colgroup>widens it). Confirmed by the reviewer across three regions. Canon is one line per cell (HARD). Add a<colgroup>taking width from Severity and Open-since, plus nowrap/ellipsis.:852— the “Every site is healthy” empty state omits.empty-ic. Every other.empty-wrapin this same file has the icon, as does Polaris’semptyState()helper. Add the circle with an outline glyph before.empty-t.
Batch 4 outcome — the programme is complete
Section titled “Batch 4 outcome — the programme is complete”Both findings fixed, four surfaces built (#pg-regions, #pg-facilities,
#pg-issues, #pg-reports), the dormant #pg-region switched on, design.md
written, the placeholder page gone. Lint 0 on both files; flow-probe passes on all
six views at 1440; 442 cells measured with no wrap, no truncation, no overflow.
The monitor-only decision met the new probe head-on, and survived. The probe
requires every table row to be clickable once a table has rows; batch 1 had enforced
monitor-only as “no .pg-actions, no row click, no advance function”. Overview
and Expansion both failed. Resolved by separating navigation from authority:
- Every row now opens a read-only view. A site, a cluster, an issue and a
project each open a centred
.dlgwhose footer holds Close plus a line naming who does act (“Stages are advanced by the Facilities PM”). No dialog has a primary button, and no advance/assign/approve/resolve function exists in the file — verified at runtime, not asserted. That is a stronger guarantee than the absent row click was, because it survives someone adding a row click later. #pg-regionwas built, verified and deliberately unreachable, waiting on “the PM’s region-detail feature”. Batch 4’s Regions roll-up is that feature, so the row click was enabled rather than re-built.
Deriving from one SITES list found a real bug the reviewer had not. All 38
sites now live in one array and every roll-up computes from it. The hand-written
per-cluster figures had drifted: South-East’s two clusters both read 88% when
their own site counts give 75% and 100% — the region average, copied down a
level. Same class as batch 3’s Overview/directory mismatch, so treat a stored count
in these flows as suspect by default.
Cross-flow data was contradictory and is now aligned. The estate placed “Sango
Retrofit Station” in the Ikeja cluster; facilities-manager has Sango as a Charge-Swap
Station in Ogun, with Ijoko as Ogun’s second needs-attention site. Batch 4 adopts
batch 3’s model, and ISS-301/ISS-302 keep their ids and titles. The dead
REGIONS.clusters/REGIONS.sites arrays that held the contradiction were removed
rather than left as stale data beside the derived version.
Still open, deliberately not fixed here: the VP names South-West’s clusters Ikeja / Ogun / Lekki while facilities-regional-manager names them Ogun / Lagos-Mainland / Lagos-Island. The individual site names match; only the cluster labels diverge. That is a fleet-wide reconciliation across three flows, not a batch-4 edit. Also note issue ages are per-flow snapshots (the VP shows Agbara at 5h, facilities-manager at 1h) — ids, titles and severities are the load-bearing part and those match.
PR #73 design review — triage
Section titled “PR #73 design review — triage”The CI design-review on #73
returned 1 critical, 9 major, 6 minor across 7 files against a diff of
10 insertions and 15 deletions — a display-name rename plus a dead-shim removal.
None of the findings were introduced by that diff. The reviewer audits whole
files, so touching one line in seven files pulled every pre-existing finding in all
seven into the report. This is the Layer 1 diff-scope trap reappearing at Layer 2.
Most findings are legitimate as canon observations, so they were routed to where the files are already being rewritten rather than bolted onto a rename:
| Finding | Routed to |
|---|---|
facilities-manager ×3 (dead fac-foot, .rt, .facts) |
Batch 3 — fixed |
facilities-vp ×2 (wrapping Reason column, missing .empty-ic) |
Batch 4 — fixed |
| facilities-station-manager ×5 | Whoever moves that flow to its new department |
starter-template — missing .tbl-foot on Batteries, #1a1a1a literals |
Standalone fix; #1a1a1a verified still present at :374,376 |
facilities-location-scout .dw-status, facilities-regional-manager .pg-actions status text, facilities-pm table <colgroup> |
Fleet-wide, own PR — .dw-status is used by every details page in those flows, so one flow must not diverge alone |
The CRITICAL is a false positive
Section titled “The CRITICAL is a false positive”It asks for starter-template’s sign-in to return to a peach var(--login-bg) ground
with a .login-left/.login-right split. That is not drift. Two deliberate commits
put it where it is:
fed28ea“revert login to the composed v1 scene, on a grey ground” — moved the ground from peach to--grey1and explicitly retuned everything that had been tuned warm: texture dots#E2D3C2→#DEDEE4, depth pool--amber-dim→--brand-dim, and all four shadow tints to neutral.c8aa34ethen added dark-theme grounding for that same scene.
The file matches that intent exactly (#scr-login{background:var(--grey1)},
.login-card{box-shadow:none}). Applying the fix would revert both commits. The rule
the reviewer cites describes the split login that fed28ea intentionally replaced, so
the canon reference is what is stale — not the file.
Traps hit in batch 1 — worth not repeating
Section titled “Traps hit in batch 1 — worth not repeating”Lint passed on all of these; they were caught by reading the canon and measuring.
- Never invent a class. I used
.cell-main/.cell-sub; the real ones here are.td-title/.td-sub. Grep the target file before writing markup. toast(msg,type)variants aredefault|error|gray|success|warning.'warn'renders unstyled and silently.- No buttons inside table cells. Not a pattern any of the six facilities flows
uses — all per-row actions are
<tr onclick>into a details view, with create/edit/confirm in a centred.dlg. Two buttons in a cell also overflowed it. - Measure table width, do not eyeball it. Compare
.tscrollscrollWidth - clientWidthagainst an existing reviewed table in the same flow (facilities-pm’s issues table sits at 8px). A 6th column collided “Target facility type” into “Stage” and clipped “Target handover” at 1440. Cut redundant columns instead of shrinking type: drop anything the row’s.td-subalready carries, and keep it searchable. - Shared components must not diverge per flow.
.tl-step-*and the project dataset were copied verbatim so one project reads identically to all three personas. Same reason the login illustration’s warm neutrals were left alone — they are byte-identical across all six flows, so they are canon, not drift.
Verifying
Section titled “Verifying”The file:// preview pane serves a stale snapshot and will not re-execute JS —
new tabs and new filenames do not help. Serve over HTTP instead:
node -e "const http=require('http'),fs=require('fs'),path=require('path');http.createServer((q,s)=>{const p=path.join(process.cwd(),decodeURIComponent(q.url.split('?')[0]));fs.readFile(p,(e,d)=>{if(e){s.writeHead(404);return s.end('nf');}s.writeHead(200,{'Content-Type':p.endsWith('.html')?'text/html':'text/plain','Cache-Control':'no-store'});s.end(d);});}).listen(8099,()=>console.log('8099'))"Then load http://localhost:8099/products/atlas/flows/<flow>/index.html, resize the
viewport to 1440, and drive the surface. Also note the pane strips ?query, so
?page= deep links cannot be tested through it — call navTo() directly.
Open, not part of any batch
Section titled “Open, not part of any batch”- PR #73 (
atlas/facilities-pm-parity) —design-reviewis red, triaged above. Its one blocking finding is a false positive; the rest are pre-existing and routed into batches 3 and 4. The rename itself is unaffected. - Station Manager’s destination department, which gates its
products.tscategory and README row. .dw-row→ canonical.td-rowmigration across the facilities flows. Deferred deliberately as a coordinated fleet-wide sweep, not a per-flow change. As of the follow-on below, facilities-pm instantiates.td-rowin new markup while its existingdwRowcall sites still render.dw-row, so the two helpers sit side by side there until the sweep runs.
Follow-on: the SOPs & Standards surface is removed (29 Jul 2026)
Section titled “Follow-on: the SOPs & Standards surface is removed (29 Jul 2026)”The SOPs & Standards sidebar item and its library page are gone from all four personas that carried them: facilities-pm (which also loses the authoring dialog), facilities-manager, facilities-regional-manager and facilities-station-manager. The Estate Manager’s Requirement SOPs tab was deliberately left alone — it reads siting criteria, not operating standards, and its case reports are filed against them (FR-6).
Scope was nav item plus its page, not every SOP reference. Standards still appear where a record puts them in context: a site record’s SOPs tab, the requirement SOP a location is surveyed against, the SOP an issue names. What is gone is the library and, on the PM, the ability to author, version and publish.
Judgment calls worth keeping:
- The
Standardssidebar group label went with the item. An.sb-groupheading over nothing is a collapsed-rail artifact, not a nav section. #pg-standardstayed. The site record’s SOPs rows are its way in, andflow-proberequires every table row to be clickable, so deleting the details page would have forced those rows to become dead cells. It is a details page reached from a row now, which is how#pg-issueand#pg-requestalready work.- The back trail says Facilities, not Facility record.
rememberPage()only tracks top-level pages, so back from a standard lands on the directory, exactly as it does for an issue opened from a record. The crumb was set to match the button rather than the other way round: two controls next to each other must not disagree. - facilities-regional-manager’s record SOPs panel had to be rebuilt to derive. It
was three hardcoded rows with no
onclick, harmless while a library existed and an orphaned#pg-standardonce it did not. It now derives fromSOPS, filters to the standards the site’s cluster is actually asked to acknowledge, and shows that cluster’s state. - The station manager’s “SOPs to read” KPI stayed. Acknowledging standards is still that role’s job, and the card lands on the same Core-tier placeholder its three siblings do. Flagged rather than removed, because dropping it would leave three cards in a four-track grid.
The PRD and the prototype now disagree on purpose. Authoring standards is still
assigned to the PM in facilities-pm/PRD.md. Removing a surface does not retire a
requirement, so the PRD was left as the record of it and design.md says so
explicitly.
Pre-existing probe failures, surfaced but not fixed (later fixed — see the
follow-on below). When the tab-removal landed, flow-probe still failed on
facilities-regional-manager’s equipment and contractors views and the station
manager’s dashboard, all with “every row clickable” — those renderers emitted
<tr> with no onclick at HEAD, before this change. They were called out as a
feature decision rather than part of removing a tab, then done in the next follow-on.
Follow-on: the PM dashboard stat row, and derived badges (29 Jul 2026)
Section titled “Follow-on: the PM dashboard stat row, and derived badges (29 Jul 2026)”A tier-by-tier comparison of facilities-pm against facilities-vp turned up four contradictions, all of the same kind: a count typed into markup, next to a surface that computes the same thing correctly. Batches 3 and 4 derived everything they built; the two older surfaces were never swept.
| Found | Fixed |
|---|---|
facilities-vp sidebar Issues & SLAs badge read 7; ISSUES holds 8, and the KPI and table foot both said 8 |
renderSbCounts() derives all three VP badges |
facilities-pm Overview Open Issues read 17 against a 12-issue array |
card dropped from the dashboard; the sidebar badge now derives to unresolved-issue count |
facilities-pm sidebar Facility Issues read 17 |
derived, so removing the KPI did not just move the wrong number one panel left |
| facilities-pm Overview restated counts the sidebar badges already carried | Open Issues and Equipment replaced by Resolution velocity and Projects |
renderOverviewKpis() is called by each surface renderer, so approving a
resolution, deciding a request or accepting a location moves the dashboard without
a reload — verified by driving each path, not by reading the code. projStats() is
the one definition of the project counts, read by both the Projects stat row and
the dashboard card.
Resolution velocity is derived from timestamps that were already there. The
issue logs carry "x ago" strings and nothing else, so agoDays() parses those
rather than adding a second and disagreeing clock. Filed to resolution approved
across the three issues whose logs carry an approval gives a median of 3.0d;
the reopened one counts, because its first resolution still took the time it took.
The sub-line states the sample size on purpose.
Still open, and not fixable by editing a KPI. facilities-pm’s facilities array
holds 12 sites while every headline on the flow says 38, which is why
Facility Health stays typed at 92% · 35 of 38 and why it cannot be reconciled
with the VP’s derived 82% · 31 of 38. Same root cause as the two flows
disagreeing about which sites exist at all: of the PM’s 12 facilities only 5 appear
in the VP’s 38-site estate with matching name, type and region, and the VP’s type
histogram carries no Retrofit Station at all (Charge-Swap 21 · Workshop 6 · BAU
5 · Experience Centre 3 · Office 3) though the PM has two and the product ships a
Retrofit Station Manager persona. Fixing that means deciding the canonical estate
once and rebuilding both datasets from it.
Follow-on: reading the case report in Review location (29 Jul 2026)
Section titled “Follow-on: reading the case report in Review location (29 Jul 2026)”FR-8 asks the PM to accept or send back a submitted location, and the whole basis for
that call is the case report the Estate Manager filed against the requirement SOP
(FR-6). The Review location .dlg named the report (REP-112 filed 21 Jul 2026 by Yusuf Idris) and stopped there, so the decision was being asked for without its
evidence. A Read case report button now unfolds the report in place: its fields as
.td-rows, the case notes in a .readonly-box, and the photo/video evidence as a
captioned .media-grid.
Three judgment calls worth keeping:
- Unfold in place, do not stack a second
.dlg. The overlay z-index andcloseModal’s “is any overlay still open” guard both allow stacking, but the flow’s Escape handler closes every open overlay at once, so a stacked report would take the half-typed Note to the Estate Manager with it. Unfolding also keeps the report and the note in one scroll, which matters because the note is written about the report. The panel folds away on every open, so the dialog still reads as a decision. caseReportmirrors the Estate Manager’sREPORTSshape (title · status · notes · media) rather than inventing a PM-side model, on the same “one record reads identically at every tier” rule batches 3 and 4 followed.- Captions are held to one line. A 72px
.media-tilefits about 13 characters and a wrapped caption covers half the thumbnail it labels..media-capgot the same nowrap + ellipsis guard the tables use, and two names were shortened so nothing truncates at all. The reference flow wraps here; that is a defect, not canon.
Measurement trap, again, and the opposite way round from batch 4. Counting
Range.getClientRects() per text node reported two lines for a caption that renders
on one: under overflow:hidden the rects fragment at the clip boundary. And
scrollWidth - clientWidth rounds to an integer, so it reported 0 overflow for a
caption overflowing by 0.04px, which is still enough to trigger the ellipsis and eat
two characters. What actually held: the element’s rendered height for wrapping (18px
is one line, 36px is two), and the text node’s Range width against the padded
content-box width for truncation.
Not ratcheted into a rule. A wrapped .media-cap is layout-only, so it would be a
flow-probe check, and flow-probe has no baseline mechanism — the reference flow and
every flow that copied it would start failing at once. It needs a staged
canon-rules.mjs rule plus a baseline refresh, the way web-dead-pager was
introduced, which is a fleet change and not this one’s to make.
Still open on this thread. The PM’s queue references REP-112 and REP-115, and
the Estate Manager flow’s own REPORTS array stops at REP-108 and holds no report
for Ifo corridor or the Agbara industrial belt. The two flows agree on the report’s
shape but not yet on its existence, which is the same class of cross-flow drift
batch 4 fixed for issue ids. Fixing it means editing the Estate Manager’s AREAS and
REPORTS, so it is left out of this change.
Follow-on: the three “every row clickable” failures, fixed (30 Jul 2026)
Section titled “Follow-on: the three “every row clickable” failures, fixed (30 Jul 2026)”The tab-removal follow-on left three probe failures standing, called out as feature decisions. This closes them, each with the target the tier already implied — not a uniform retrofit.
| View | Row now opens |
|---|---|
| facilities-regional-manager · equipment | the site holding the item, on its Equipment tab — openRecordByName(site,'rt-equipment'), extending the record opener to take a tab, mirroring the cluster tier exactly |
| facilities-regional-manager · contractors | a compact read-only .dlg (Close only) whose Clusters field links each covered cluster into its record via openCluster — the same shape the cluster tier’s contractor dialog uses |
| facilities-station-manager · dashboard | a read-only issue peek .dlg (Close only); the station files and reads issues, it does not resolve them, so the dialog has no action |
Three design points worth keeping:
- The target followed each tier’s authority, not a template. Equipment is a facet of a site, so its row routes into the site record — there is no standalone equipment record at any tier. A contractor and a station issue are compact records with nowhere richer to go, so they open centred dialogs. Every new dialog is read-only: Close alone, no primary button, because neither the region tier (over a contractor) nor the station tier (over an issue it did not action) has authority to act there. Asserted at runtime, not inferred.
- Data joins were checked before routing. All 12 equipment sites in the region
flow resolve to a
FACILITIESentry, so no equipment row lands on a missing record. The contractorclustersstring (“All 3 clusters”, “Lagos-Mainland, Lagos-Island”) is parsed againstCLUSTERSby name, with “all” expanding to every cluster. - The new dialogs were wired into each flow’s Escape handler.
flow-probe’s “every dialog closes on Escape” check opens every.dlg-overlayand dispatches Escape; a dialog the handler does not know about fails it. Both handlers now close the new dialog alongside the role switcher.
The station-manager’s dashboard issue table was three static <tr> with a dead pager;
it is now rendered from a small STN_ISSUES array so each row carries its own target
and the count derives. Its ids reuse ISS-302 / ISS-304 from the cluster and region
tiers so one issue reads identically everywhere; ISS-311 is a station-local
follow-on. flow-probe now passes every view on all five facilities flows at 1440,
and both edited flows lint 0 with no console errors. The station-manager has no
design.md (it is “Core · full build to follow”), so only the regional flow’s doc was
updated.
Layer-2 canon review, one finding fixed. An independent design review of the three
changed flows (Layer 1 was already 0 errors) turned up a single re-skin: the station
peek dialog first rendered its Severity / Status / Assignee / Age fields with .hc-row
— the dashboard’s health-check row — because that was the only key/value-ish atom I
had reached for. The flow already defines the canonical .td-row/.td-k/.td-ic/
.td-v (Polaris tdRow), so the fix was to add the tdRow helper and its four icon
constants and render the fields with the real atom; .hc-row now appears only on the
Pass/Fail health card where it belongs. The regional contractor dialog (.dw-row via
diRow, matching that flow’s own detail rows) and facilities-pm were clean.