Checkpoint 0.1.10 · complete-design review · not final · source-only · browser review pending
MainsKeeper — complete design notes
What was built, where it came from, the behaviour that isn’t obvious from the screens, the decisions, the assumptions and the known issues. Start with the complete design review. Round 6 comes first, then round 5; the round-4 notes follow, corrected where later rounds changed them.
Nothing in rounds 4, 5 or 6 has been rendered or visually inspected, and there are no screenshots. You asked for render inspection and screenshots. Rendering is not permitted in this environment: pages were written from source, and opening, previewing or screenshotting them was not allowed. The review page lists an unverified visual checklist and the screenshot routes to capture. Contrast was calculated from token values, not measured on screen. Layout at 375 / 768 / 1024 / 1440 px is written into the CSS but has not been looked at. The first review pass should be visual.
Round 9 · checkpoint 0.1.10 — exact monitoring-gap intervals (source-only checkpoint)
Fixed: in 0.1.9 the series and aggregate found monitoring gaps on the 1-minute sample grid, then snapped each one to the first recorded stop it overlapped. A 30 s gap (disable, check in, re-enable, check in: 15:22:27–15:22:57) contained no sample, so the series and aggregate had no gap, the chart joined across it and availability said “No unknown time”. Two gaps 15 s apart (15:22:27–15:28:27, 15:28:42–15:34:12) produced a single sampled run and the second gap was lost. Now fixture.js takes the persisted intervals (monitorStops / monitorResumes → u.outages plus an open gap while disabled) as authoritative. Every gap goes into the series, power lanes, history() and aggregate unknown spans with exact boundaries. Chart segments split at each boundary, points inside a gap are dropped, and the observed interval between gaps keeps no invented points. Availability and hourly-bucket coverage come from exact intersections of the power-state intervals with the selected range (stateMinutes), in fractional minutes. Only the text is rounded (fmt.duration, new fmt.minutes), and a non-zero gap never reads “No unknown time”.
- Source / Node checks:
state-checks.js203 passed · 0 failed. That is 180 from 0.1.9 plus 23 in the new group 9: reproductions A and B on the created desk UPS and the fixture BW Rack UPS (bw-pi-01), clipped ranges 15:22–15:23 / 15:25–15:30 / 15:28:30–15:28:40, agreement between series, lanes, history, aggregate and availability, later readings, a third cycle, and a reload. CLI generator self-check passed; 151 exports. All scripts passnode --check; local links resolve. - Expectations corrected explicitly: group 3, Office Desk UPS 15:00–15:22, is now 4.2 min observed and 17.8 min unknown (was 5 / 17 on the minute grid). Group 8’s two-gap availability is now exactly 11.0 min; it was “≥ whole minutes − 1”, which accepted the 0.1.9 figure of 10 whole minutes for 11.0 min of gaps, and that tolerance is removed. Group 8’s aggregate check now requires the exact total. The round-7 coverage figures below are corrected in place, with the old values noted.
- Display: hourly “reduced coverage” rows and bucket tooltips used to print raw minute counts. They now use
fmt.minutes(“30 s”, “17.8 min”, “4.2 min of 22 min”). - Not done here: browser review (journeys, 375/768/1024/1440 px in both themes, focus, dialogs, overflow, chart legibility, screenshots). Rendering is not permitted in this environment, so this checkpoint is source-only and the unverified checklist stays open.
Round 8 · checkpoint 0.1.9 — persistent monitoring gaps (source-only checkpoint)
Fixed: every UPS-role-off window now keeps its own gap with stable boundaries — the last observation before monitoring stopped (recorded when the role-off applies at check-in, monitorStops) and the first observation after it resumed (recorded by the first reading after re-enable, monitorResumes). Before, store.js overwrote readingsAt on every reading and fixture.js kept only the latest window (.pop()), so a later reading stretched the gap (15:22:27–15:27:57 became …15:33:27) and a second cycle erased the first gap. Series, power lanes, aggregates (snapped to the first overlapping stop in 0.1.9 — superseded by exact intervals in 0.1.10, see round 9), history unknown spans and availability all use the same gap list, which is stored in the browser and survives reloads.
- Source / Node checks:
state-checks.js180 passed · 0 failed at 0.1.9 (169 earlier unchanged + 11 in new group 8, including the fixture BW Rack UPS keeping its own two gaps; cycle 1 gap 15:22:27–15:27:57; a later reading leaves it unchanged; a second cycle adds 15:33:27–15:38:57 and keeps the first; lanes, history, availability and aggregate agree; a reload keeps both). One new check’s first draft expected availability in fractional minutes; availability counted whole minutes (10 for 11.0 min of gaps), so the expectation was loosened. 0.1.10 reverses this: availability is exact (11.0 min) and the tolerance is removed. CLI generator self-check passed; 151 exports. - Not done here: the browser review you asked for (journeys, 375/768/1024/1440 px in both themes, focus, dialogs, overflow, chart legibility, screenshots). Rendering is not permitted in this environment; the unverified checklist stays open.
Round 7 · checkpoint 0.1.8 — source-only correction pass
Fixes to shared logic and copy only; the visual direction, feature scope and concepts 1–9 are unchanged. Browser QA was deferred to a separate browser-capable environment and does not block this checkpoint, but it does block acceptance. Routes and expected outcomes: review · 0.1.8 corrections.
Fixes
- UPS role transitions. The applied UPS role now governs every observation.
fixture.jsno longer refreshes a UPS timestamp just because its collector is live: when the UPS role is applied off, every UPS read by that collector (fixture and created) keeps its last real reading and time, shows “Monitoring disabled · last known HH:MM” (UPS status, freshness badge, source state “Monitoring disabled — UPS role off”), and the power lane, series, hourly aggregates, history unknown spans and availability carry “No records — UPS monitoring disabled” as unknown. Re-enabling stays disabled while pending; at the check-in that applies it, one new reading resumes monitoring and the disabled span remains a gap. The store keeps the applied roles history (roleLog) and last store-recorded reading (readingsAt) so the window survives later changes. - Deferred activation.
checkInno longer callsactivatedirectly. Immediate (selectSource) and deferred (check-in after a queued test) activation shareactivationPrerequisites: successful read test → identification (run at check-in if needed) → duplicate reconciliation → confirmation. For SNMP 10.0.20.16 the reconnect identifies the APC SMT1500 card (serial AS1811240377) and stops at “Identified — confirm possible duplicate” (BW Rack UPS); the saved choice and name are kept; no UPS asset is created until you confirm. - No invented measurements.
recordReadingsno longer defaults a missing charge to 100 %; a value the device does not report stays null (“Not reported”). - Copy. ESP32 local storage, retention and logging are described as fixed in firmware (badge “Fixed in firmware”), matching the capability rows. Generic contact-loss wording now states the heartbeat-dependent grace, max(5 min, 3 × heartbeat); incident timelines keep “5 min grace”, which is what that rule gives for their 30 s heartbeats. The witness-down coverage figures were recomputed per range (below); the 0.1.7 “23 minutes / 18 unknown” sentence and its open question were removed.
- Review aids. Collector review controls gain “Advance demo clock 5 min” (
MK.store.advanceClock) and report check-in outcomes “not started: … confirm possible duplicate” and “readings resumed”. Network setup shows a confirm step for a choice that is waiting on duplicate confirmation after a reconnect.
Witness-down coverage, Office Desk UPS (computed with MK.select.availability / aggregate)
- 14:30–15:22: 52 min · 34.2 observed on utility · 17.8 unknown (15:04:12–15:22) · coverage 65.8%.
- 15:00–15:22, and the 7-day view’s 15:00 hour bucket: 22 min elapsed · 4.2 observed · 17.8 unknown · coverage 19.1%.
- Last 24 h: 1,440 min · 17.8 unknown · coverage 98.8%.
- Corrected in 0.1.10. 0.1.8 and 0.1.9 quoted minute-sampled values (35 / 17 / 67.3% and 5 / 17 / 22.7%). The figures above are exact interval intersections; the UI text shows “18 min unknown”.
What was exercised (source / Node checks — not browser verification)
"$OD_NODE_BIN" product/app/assets/state-checks.js: 169 passed · 0 failed. The existing 136 checks are unchanged and pass. New group 6 (19 checks) covers role on → off → on, including fixture BW Rack / BW Office UPSs; new group 7 (14 checks) covers the queued SNMP 10.0.20.16 path, the immediate path and the no-default-charge rule. No existing expectation had to change."$OD_NODE_BIN" product/cli/build-examples.js: self-check passed; 151 example files regenerated (unchanged count).node --checkon every changed script.- Not exercised: browser journeys, layout at 375 / 768 / 1024 / 1440 px in both themes, keyboard focus, dialogs, overflow, chart labels, browser errors and screenshots. The unverified browser checklist remains open.
Remaining limitations
- While a UPS is monitoring-disabled, open incidents about it (for example INC-0412 on BW Rack UPS) are not re-worded; their evidence simply ends at the last reading.
- The role history is kept only for changes made through the store in this browser; fixture roles have no earlier history.
- The CLI has no screen for “monitoring disabled” or “confirm possible duplicate” after a reconnect; its exports are unchanged.
Round 6 · checkpoint 0.1.7
This was a correction round. Visual direction and feature scope are frozen. The corrections live in shared logic (product/app/assets/, CONTRACT.md §18), and pages, onboarding and the CLI were migrated onto it. Pages no longer compute duplicates, ranges, unknown spans, freshness thresholds or platform strings themselves. Routes, steps and expected outcomes for every case are on the review page: 0.1.7 corrections.
Decisions
- Network UPS identity
- Identity = namespace | protocol | host | port | NUT UPS name (SNMP has no UPS name). The namespace is the collector’s override, else the site’s (
broadwater-lan,home-lan,shop-lan,duplex-lan; created sites<id>-lan, e.g.cabin-lan). Loopback hosts are per machine. A host alone is never an identity. Hard duplicate (same identity, monitored or draft) is blocked: “Already monitored by bw-pi-01 as BW Office UPS”. Soft duplicate (a trustworthy serial matching another UPS at a different identity, same vendor and base model) is a question, never a block: “Possibly the same UPS as BW Rack UPS (serial match) — confirm”. Untrustworthy serials (empty, placeholders, one repeated character such as000000000000, sequential runs, under 6 alphanumerics, equal to the model, or unknown vendor/model) decide nothing. - Collector availability and UPS-role gating
- Enforced in the store, so buttons cannot bypass them. Saving drafts and disabled sources is always allowed. Testing needs a reporting collector; otherwise the test is queued (“Test queued — collector not reporting”) and runs at the next check-in. It is never shown as success or failure. Monitoring needs a reporting collector and an applied UPS role. Otherwise the choice is saved as “Configured — waiting for collector” (which takes precedence) or “Configured — waiting for UPS role”, and starts at the check-in where both hold. Readings start no earlier than the reconnect or role-application time. Review controls (Simulate collector reconnect / check-in / disconnect) stand in for the device. Each reconnect or first reading advances the demo clock 30 s.
- History range semantics
- A point is included if from ≤ observed time ≤ to. An interval is included if start ≤ to and end ≥ from, where end = the end time, else the last observed record, else now only while ongoing, else the start. There is no implicit “until now”. Replayed records sit at observation time, and receipt events at receive time. So 15:00–15:22 excludes yesterday’s Duplex episode (INC-0409), Home 06:42 and the Duplex 14:03–14:30 episode.
- Unknown is data
- Missing-evidence states (“No records — collector not reporting”, “No records”, “History arriving”, “Not monitored yet”, “Not reported yet — next report due by …”) travel through history, lanes, series, hourly aggregates, coverage and availability as unknown. They are never filled, joined or counted as up or down. Witness-down: nothing from home-studio / Office Desk UPS after 15:04:12. Site reachability (home-esp32-garage) stays separate and never stands in for Office Desk UPS power.
- Freshness by cadence
- I = the source’s effective reporting interval (10–600 s, default 30 s), g = max(30 s, I/2). On schedule ≤ I + g · Overdue ≤ 2I + g · “Stale · last known <time>” beyond that or when collector contact is lost. The absolute age and the interval are always shown (“read 7 min ago · every 10 min”). Heartbeat is judged separately: late after 2H, contact lost after max(5 min, 3H); bw-pi-01 uses a 15 s override. JSON
ups[].freshness = {state, ageSeconds, expectedIntervalSeconds, allowanceSeconds}. - Platform-specific instructions
- Every command, elevation note, config path, secret store and log location comes from
MK.cli.platformfor that collector’s platform. macOS:sudo,/Library/Application Support/MainsKeeper/collector.toml, Keychain,/Library/Logs/MainsKeeper/. Fedora / Pi:sudo,/etc/mainskeeper/collector.toml,/etc/mainskeeper/secrets.d/,journalctl -u mainskeeperd. Windows: an elevated terminal (Administrator PowerShell, no sudo),C:\ProgramData\MainsKeeper\collector.toml, DPAPI, Event Viewer. ESP32: no host CLI. Use the probe’s local setup page. - Checks
"$OD_NODE_BIN" product/app/assets/state-checks.jsdrives the store and selectors in Node and exits 1 on any failure. 136/136 pass.
Freshness badges that moved
Computed with MK.select.upsReadingFreshness for every fixture UPS in incident, calm, witness-down, ups-comms-lost and revoked, against the 0.1.6 rule (≤ 60 s Fresh · ≤ 5 min Delayed · > 5 min Stale).
- BW Office UPS (600 s cadence, read 420 s ago): Stale → On schedule (“On schedule · read 7 min ago · every 10 min”) in incident, calm, witness-down and ups-comms-lost. It is on schedule up to 15 min, overdue up to 25 min.
- Revoked scenario, BW Rack UPS and BW Office UPS (read 122 s ago, bw-pi-01 rejected): Delayed → “Stale · last known 15:29”. Contact is lost, and 122 s exceeds the 90 s limit for 30 s sources.
- All 30 s sources: On schedule up to 60 s (unchanged), Overdue 61–90 s (was Fresh above 60 s, Delayed to 5 min), Stale after 90 s (was after 5 min). No fixture UPS currently sits between 61 s and 300 s outside the revoked scenario, so no other badge changes.
- Unchanged: Shop Bench UPS “Stale · last known 15:10” (718 s), Office Desk UPS in witness-down “Stale · last known 15:04” (1068 s), Garage UPS in ups-comms-lost “Stale · last known 15:16” (320 s), and every other UPS On schedule.
- Words: Fresh → On schedule, Delayed → Overdue. The equipment filter is now
?cond=overdue;?cond=delayedstill works as an alias.
Deviations reported by the builders
- Network setup: Identify is required before Use. The soft-duplicate confirmation is page-local, so reloading the page asks again. Case (b) on home-studio and the home-laptop journey both register
home-lan|nut|192.168.1.20|3493|rackups, so reset the demo between them. - History: receipt events (e.g. Duplex replay receipt at 15:17:40) sit at their receive time. The default availability period changed. “View this episode” pads the range by 25 min.
- Onboarding: OS install lines (package names, installers) are page-local; commands and paths are shared. A connectivity-only collector given the UPS role later shows “No USB UPS found”, and there is no re-discover action.
- CLI: 151 example files (121 + 30 new). There is no queued-test screen, because the CLI runs on the collector; a local test with the cloud unreachable is shown instead.
sources useprompts on a serial match. The status-bw tables dropped the Input and temperature columns. Exit codes: duplicate 1, soft serial match on test 10, role-gated use 0, Windowsconfig setunelevated 5,help esp322.
What was exercised
state-checks.js: 136/136 at build time and again at integration.- The builders report Node fake-DOM harness checks of rendered HTML text per route, plus the CLI self-check and export generation.
- Integration ran Node probes of the selectors for the strings quoted in the review (home-laptop journey 15:22:30 / 15:22:57, witness-down coverage 67.3% with 17 min unknown at that time — 65.8% / 17.8 min exactly since 0.1.10, freshness per scenario). It also checked that every local link in the review, these notes and the launcher resolves on disk.
- Not exercised: clicks and submit wiring in a browser, chart drag, layout, visuals, contrast. Nothing was rendered or screenshotted. The review keeps the unverified browser checklist and the screenshot list (all “Not captured”).
Remaining limitations and open questions
- No fixture UPS is Overdue, so the Overdue badge appears only in the legend and the gallery. Should the fixture gain one?
- Per-source
reportingIntervalis read-only (fixture/source property). There is no dashboard edit flow for it yet. - The demo clock offset applies to all demo states; summaries computed at 15:22 (INC-0412) do not re-word themselves after reconnects.
- 1-minute series cover the last 24 h. Longer ranges use hourly aggregates, and earlier days are synthesized utility except known gaps.
- Partly resolved in 0.1.8: a confirmation needed during deferred activation now persists in the store (source status “Identified — confirm possible duplicate”, choice kept). The immediate Identify → confirm step on the network-setup page is still page-local and asks again after a reload.
- Should a collector that gains the UPS role later offer “Discover USB UPSs again” instead of “No USB UPS found”?
- Should the CLI have a queued-test screen for a cloud-initiated test, or is the collector-local test enough?
- Carried over from round 5: enroll and queue exit codes, “Resolved = recovered + 5 min stable”, the 10.0.30.5 fixture endpoint, abandoned network drafts, the 7-day custom-range limit, and
statusexiting 0 regardless of state.
Round 5 · checkpoint 0.1.6
This round finished and connected the product built in round 4. There is no new visual direction and no new feature area. The identity is still Lattice (concept 8) with Datum’s (concept 7) equipment workflows and the site-first fleet overview.
What changed
- Shared prototype state. One store (
MK.store, browserlocalStoragekeymk-proto-v1) now feeds every page. Acknowledgments, the INC-0412 restoration, created sites, collectors and UPSs, network sources and configuration changes are the same on every page and in other open tabs. Reset demo is on every page. - First run ends in your own fleet. The flow goes empty account → site → install → approval → roles & sources (USB or network) → first readings. “Go to fleet” then shows the new site, garage-pi-02 and the CyberPower CP1500PFCLCD, and their detail, sources and configuration pages resolve.
- Network UPS setup (network-source.html) for a remote NUT server or an SNMP card, with five outcomes: success, unreachable, rejected credentials, unsupported capability, already monitored. [0.1.7: “already monitored” is decided by identity, not host; a serial match adds a confirm step; tests queue while the collector is not reporting.]
- Not-found states replace every silent substitution. Pages now honour the link parameters they used to ignore (
sync.html?collector=,relationships.html?site=and?focus=). - Semantics corrected: history filtering, 7-day aggregation, alert timing, incident timestamps, backfill wording, staleness thresholds, and a witness-down scenario plus a sleeping workstation.
- CLI: one grammar with elevation; configuration ownership and network UPS sections; 121 regenerated examples, renamed so the checkpoint includes all of them (151 in 0.1.7).
Decisions
- Store
- Pages read the fixture merged with the store and keep no copies of their own. sessionStorage,
?ack=and page-local fixtures are gone. Each incident has one acknowledgment record{by, at, note}. Some review links change state when opened, for exampleincident.html?id=INC-0412&step=ack. - Datasets
- “Demo fleet” and “Your account (first run)”.
?scenario=emptyshows the account. Creating a site there makes the account the active dataset. Pages withoutstate/scenariokeep showing the account until you switch back or reset. The review bar shows the active dataset. - Parameter contract
- Links carry
state,scenario,themeandmotion. They never carryauth,enroll,id,site,collector,upsorfocus. Sign-in uses?auth=, so it no longer collides with the review bar’s?state=. Unknown ids render “not found”. - Configuration ownership
- One table (
MK.config.contract) serves collector configuration, the review and the CLI spec. Dashboard-managed settings go desired → pending → applied at check-in, or fail with a reason. Alert rules are server-side and apply immediately. Secrets stay on the collector (mainskeeper sources auth <source>, elevated per platform since 0.1.7; ESP32 uses its setup page), and the dashboard shows only Configured / Missing / Rejected; the dashboard credential-delivery flow was removed. Storage and logging are local and read-only in the dashboard. Onlyheartbeat_intervalmay be overridden locally: “Effective: 15 s (local override) · Desired in dashboard: 30 s” on bw-pi-01. - CLI grammar & elevation
- One grammar everywhere:
enroll --reenroll,sources discover,sources use.--replace,scanandselectno longer appear. macOS, Fedora and Raspberry Pi OS usesudo. Windows says “Run in an elevated terminal (Administrator)”. [0.1.7: paths, secret stores and logs are platform-specific too; ESP32 has no CLI.] Read-only commands need no elevation.logsneeds themainskeepergroup.statusexits 0 whenever the query succeeds. Network exits: duplicate 1, unreachable 6, rejected secret 6, partial 10 on test, success 0. - Network setup
- Protocol → endpoint → local credentials (status only; must be Configured before the test) → test → identify → duplicate check → use → readings. A UPS already monitored by another collector is never created twice. You choose to keep the existing one or cancel. [Superseded in 0.1.7: duplicates are decided by network identity (namespace | protocol | host | port | UPS name); a trustworthy serial match only asks for confirmation. See round-6 decisions.] NUT ships with MainsKeeper.
- Alert and timestamp model
- On battery notifies after 60 s on battery. Low runtime fires below a 10 min UPS estimate. Contact lost fires after the contact grace — max(5 min, 3 × the collector’s heartbeat interval); 5 min for the 30 s heartbeats in this fixture. UPS comms lost fires after 2 min while the collector reports. Backlog stalled fires after 30 min. Home 06:42:10–06:42:48 (38 s) is “Below 60 s delay — event only”. Labels: First evidence · Grace period started · Declared · Acknowledged · Recovered · Resolved. Resolved = recovered and stable for 5 min (INC-0411 resolved 15:07:06; INC-0412 15:36:00 in the walkthrough).
- Backfill policy
- “Historical backfill updates history at observation time and does not generate duplicate or obsolete notifications. Fresh evidence of an active condition remains alertable while a backlog drains. Ordinary transmission latency does not make a reading historical replay.” Replay = a record received more than 2 min after observation while the collector was reconnecting from its buffer. Normal latency is not replay.
- History semantics
- Site, UPS and range filters apply to charts, episodes and events. When you select a UPS, other UPSs’ episodes at the site appear only under “Site context”. 1 h, 6 h and 24 h use 1-minute data. 7 d uses hourly aggregates (min/max/mean, power-state share, coverage) and hatches hours with no data as “No records”. Example: Shop, 26 Sep 02:00–06:00.
- Staleness
- [Superseded in 0.1.7: freshness is relative to each source’s reporting interval (On schedule · Overdue · Stale · last known); see moved badges.] Round 5 rule: a reading up to 60 s old was Fresh, up to 5 min Delayed, then “Stale · last known <time>”. Collector contact is lost after the contact grace, max(5 min, 3 × heartbeat). A legend appears on UPS and collector detail.
- Witness and sleep
- Witness down (
?scenario=witness-down): home-studio stops at 15:04:12 and INC-0415 is declared, while “Site still reachable: home-esp32-garage reporting”. This is distinct from site contact loss (Shop: all witnesses silent, cause unknown) and from a locally observed connectivity failure (Duplex 13:41: probe evidence). home-laptop is an ordinary workstation, asleep since 13:05: “Asleep (ordinary workstation) — no incident”. It is excluded from “4 of 6 expected collectors reporting · 1 workstation asleep”. - ESP32 values
- ~384 KB flash ring, ~6 h at 30 s, USB HID on ESP32-S3 with USB-OTG host, max 3 targets. All are labelled “Illustrative — validated board/UPS list pending”. Nothing implies arbitrary boards or UPSs are supported.
What was exercised
- Builders ran only source-level checks (
node --check) and Node-harness tests of the store, selectors, configuration contract and CLI example generation. - No page was rendered, previewed or screenshotted. Rendering is not permitted in this environment. The visual checklist marks every journey × width × theme as not verified.
- Integration checked that every local link target in the review, the notes and the project launcher exists on disk. It also read each page’s parameter handling in source to confirm the review routes.
Remaining limitations and open questions
- The CLI success journey now matches the web because the fixture gained network endpoint
10.0.30.5(NUT, duplex-rack). Confirm it should stay in the fixture. - To confirm:
enrollexits 1 when the code expires or is denied.queueexits 0 when stalled and reports the state in its output. - Abandoned network drafts stay on the collector. Until one is removed, the collector’s credential summary can read Missing.
- One enrollment code per platform per run. Reset demo to enroll the same device again.
- Expired, denied and offline enrollment states are display-only. They do not change the store.
- The equipment table was exercised only against a stub harness.
- Dense list rows at 375 px are a layout risk until someone looks at them.
- The 7-day all-sites history SVG is heavy (many hourly buckets per site).
- Design decision to confirm: “Resolved = recovered + 5 min stable”.
- “Immediately” was removed from the on-battery rule. The options are now 30 s, 60 s, 2 min and 5 min.
- Custom history ranges are limited to the last 7 days.
- Low runtime is designed as a rule. No fixture UPS crosses 10 min, and the round-4 page-local demo was removed.
- Loading skeletons exist only on the fleet overview and history. Chart drag selection is off on touch.
statusexits 0 even when a UPS is on battery or stale. The state is inups[].powerandups[].freshness. A--fail-onflag remains an option.
What was built (round 4, updated)
- Brand (brand/): interlaced-loop logomark (dark, light, mono), wordmark, horizontal lockups, app icons, 16 and 32 px favicons and a Safari mask icon. All SVG.
- Design system (
app/assets/): tokens with dark default and a coordinated light theme, a base layer, shared components, an inline icon sprite, a fixture with derived selectors, and an app shell. CONTRACT.md documents all of it. Component gallery. - Web app prototype, 22 pages: fleet overview (landing), site, site settings, equipment workspace, UPS detail, incidents, incident detail, notifications, maintenance, alert rules, history, collectors, collector detail, relationships, sources, network UPS setup (round 5), collector configuration, history sync, sign-in, welcome, install, enroll.
- Proposed CLI contract (cli/index.html):
mainskeepercommands, exit codes, behaviour, JSON schema and terminal mockups. A single source (cli-screens.js) generates the example files at 80 and 120 columns, plus JSON: 121 in round 5, 151 in round 6. - Review launcher (review.html): what changed, ten journeys, configuration ownership, product map with direct state links, a state-coverage checklist, an unverified visual checklist, screenshot routes, CLI examples by category, a design-system summary and a scenario summary.
Provenance
You chose a direction from round 3. Concept names appear only in these notes, never in the product UI.
- Identity, palette, type and visual character come from concept 8, Lattice (identity, dashboard): the interlaced-loop mark, a dark ground (#101418), mint for live and observed things, sodium orange for brand and selection, Sora and DM Mono, and the relationship-and-coverage view that became relationships.html.
- Equipment workflows come from concept 7, Datum (dashboard): filter, sort, compare, and an inspector that keeps your place. They were rebuilt in the Lattice visual language as equipment.html. Datum’s lettering-led identity was not carried over.
- The landing page is a new fleet overview organised around sites. Neither concept had one in this form.
- Concepts 1–7 and 9 stay as unchanged references (project launcher).
Non-obvious behaviour
- Recovery is independent of sync. An incident recovers on fresh evidence: new contact plus probe or UPS readings. INC-0411 recovered at 15:02:06 while 1,248 records were still waiting to upload. The backlog appears as its own attribute and never holds the incident open.
- Backfill and replay (corrected in round 5). Historical backfill updates history at observation time and does not generate duplicate or obsolete notifications. Fresh evidence of an active condition remains alertable while a backlog drains. Ordinary transmission latency does not make a reading historical replay. A record received more than 2 min after observation while the collector reconnects is tagged “Received late · replayed”. It is shown at its observation time with a hollow marker and the time it was received. The 14:03 Duplex battery start, received 15:17:40, raised no notification. Old readings never replace a newer current state.
- The first observation after a gap (Duplex 14:52:10) is marked uncertain (±90 s) until the clock syncs at 14:54. When utility came back is unknown, somewhere between 14:30 and 14:52. The episode reads “At least 27 minutes on battery; endpoint unknown”. Utility returning never establishes a maximum runtime.
- Episode receipt times are stored for both the start and end records (for example, Office Desk 06:42:10 received 06:42:11). The UI shows them when they explain a delay.
- Availability math. Observed internet availability and reporting availability are separate figures. Each shows its period, coverage and unknown minutes, and states that maintenance is excluded. Observed availability = 1 − down ÷ (period − unknown − maintenance): unknown time is reported as reduced coverage and never counted as up or down. Home 30 d: 99.93% observed, 98.1% coverage (13 h 41 min unknown), 99.4% reporting, 0 h maintenance.
- Coverage model. Every UPS, measurement and piece of protected equipment is in one of four states: directly monitored; relationship recorded — no direct monitoring expected; measurement unsupported; expected collector/source missing. A sleeping workstation with no agent (bw-desk-ws) is normal and not a fault. Reads, powers and checks links are labelled “configured”.
- Dashboard-managed vs local settings (one contract in round 5; see Decisions). Examples: local, managed (rejected, exit 1), heartbeat override, effective values. The browser never sends commands to a device.
- No remote commands. No page or CLI command offers shutdown, reboot, outlet switching, self-test or power settings. Alert thresholds only configure notifications. Fault guidance says what to check physically.
- ESP32 vs host retention. ESP32 probes keep a ~384 KB flash ring buffer (about 6 h at a 30 s interval) and read USB HID only. These ESP32 values are illustrative: validated board/UPS list pending. Pi, Mac, Windows and Fedora hosts use a disk buffer with a cap (512 MB–2 GB in the fixture) and 30-day retention. Storage pressure and dropped records are designed (pressure, lost).
- Enrollment security. The device shows a short user code (for example WDJB-MJHT, 10-minute lifetime). The person approves it in the browser at
https://mainskeeper.example.net/enrollaftermainskeeper enroll. The device’s polling secret and the issued credential never appear in a URL, page or CLI output. Revocation is immediate (uploads rejected with 401). Re-enrollment (mainskeeper enroll --reenroll) issues a new credential and keeps history and UPS associations. - Contact age vs reading age. Collector contact and UPS reading freshness are shown as two separate values everywhere. Readings are On schedule, Overdue or “Stale · last known 15:10”, judged against their own reporting interval, with their age and interval (round 6).
Assumptions
- Installers (.pkg, .msi, RPM, Pi image, ESP32 firmware) and supported hardware are design targets, not built artifacts.
- Authentication is Cloudflare Access with GitHub only. There are no local passwords.
- All fixture times are PDT (America/Los_Angeles, UTC−7). “Now” is Wed 30 Sep 2026 15:22:00, or 15:32:00 in the revoked scenario. The INC-0412 walkthrough moves the demo clock to 15:32 (restored) and 15:37 (resolved). The first-run account has its own clock that advances 30 s per action.
- The CLI is a proposed contract: commands, flags, exit codes and JSON shapes for review, not an implementation.
- All data is fictional demonstration data. Hosts, addresses and people (jp, sam) are invented.
Known issues and unresolved design assumptions (round 4, status in round 5)
Round-5 status is in brackets. Open round-5 items are listed under Remaining limitations and open questions.
Verification
- No rendering, visual inspection or screenshots (see the note at the top). Tablet (768–1023) layouts and the 375 px mobile acknowledge bar have not been checked. [Still open.]
Behaviour and data
- Some links between pages pass parameters the receiving page ignores. [Fixed in round 5: see Cross-link parameters.]
- Onboarding links for new collectors pointed at entities the fixture lacked;
sources.htmlfell back to bw-pi-01 silently. [Fixed: created collectors live in the store; unknown ids show “not found”.] - The sign-in page used
?state=, which the review bar overwrote. [Fixed: sign-in uses?auth=.] - INC-0412’s “restored” values were local to the page. [Fixed: the walkthrough is in the store; every page shows 15:31 restored, 22 min observed, resolved 15:36.]
- 7-day ranges drew only 24 h of detail. [Fixed: hourly aggregates with coverage.]
- Acknowledgments persisted only through sessionStorage or
?ack=. [Fixed: one record per incident in the shared store.] - Chart drag selection is turned off on touch; use the range form instead. [Still open.]
- “Stale” had no threshold of its own. [Fixed in round 5 with fixed thresholds; replaced in round 6 by cadence-relative On schedule · Overdue · Stale, with a legend.]
- No fixture site showed one collector silent while another kept reporting. [Fixed: witness-down scenario.]
- Loading skeletons are shown only on the fleet overview (
?loading=1) and history (?demo=loading). [Still open.]
CLI
- Some fault screens use example hosts lab-pi and lab-win rather than fixture collectors.
- Checkpoint 0.1.5 left out three CLI examples whose names tripped the packager’s credential guard. [Fixed: renamed to error-enrollment-revoked.json and sources-access-rejected.80/120.txt; checkpoint 0.1.6 included all 121 examples; 0.1.7 includes all 151.]
mainskeeper statusexits 0 whenever the query succeeds (decided in round 5). The state is in the output and in JSONups[].power/ups[].freshness. A--fail-onflag is still an option.
Visual and foundation deviations
- The terminal panel in onboarding stays dark in the light theme.
- Icons are injected inline so pages work from
file://. Theicons.svgfile is a reference copy only. - Warning colour (#E8D85C dark / #6E6100 light) was moved away from sodium so it can’t be read as selection. “Checks” links are blue (connectivity).
- The revoked scenario’s clock is 15:32. The empty scenario is derived from calm.
- INC-0413: last contact 15:10:02 (grace period started), first evidence 15:10:32 (first missed heartbeat), declared 15:15:02 after the 5-minute grace. “Opened” is no longer used.
- Settings has no hub page; the nav item lands on alert rules.
- Chart hatch patterns follow the document theme, not a nested theme pane. This affects only the gallery’s side-by-side view.
- Orchestrator integration fix (round 4): the enroll command is
mainskeeper enroll, the URL ishttps://mainskeeper.example.net/enroll, and the code lifetime is 10 minutes (expires 15:29:30). Round 5 integration fixes: network endpoint 10.0.30.5 added so the CLI success journey matches the web; not-found wording when no id is given.
Cross-link parameters (checked in page source, round 5)
| Link (from) | Receiving page reads | Result |
|---|---|---|
sync.html?collector= (fleet overview, history sync section) | collector, case, step | Honoured; unknown → not found |
relationships.html?site= (site detail) | site, focus, cov, links | Honoured; filters clusters; unknown → not found |
relationships.html?focus= (UPS detail, collectors) | focus=<kind>:<id> | Honoured; unknown focus → message, nothing preselected |
sources.html?collector=garage-pi-02 (enroll, welcome) | collector, case | Honoured for created collectors; unknown → not found |
collector-config.html?id=garage-pi-02 (enroll, welcome) | id | Honoured for created collectors; unknown → not found |
network-source.html?collector=&from=welcome&return= (welcome step 7, sources) | collector, source, from, return | Honoured; return accepts relative *.html only |
sign-in.html?auth= (shell sign-out) | auth, return | Honoured; state left to the review bar |
maintenance.html?site= (incident detail) | site, edit, new, date, start, end | Honoured |
history.html?site=&range=6h (site), ?ups=&range= (UPS), ?site=&ups=&from=&to=&sel= (incident) | site, ups, range, from, to, sel, ep | Honoured |
sources.html?collector= (collector, UPS detail) | collector | Honoured |
incidents.html?ack=, relationships.html?sel=, ups.html?runtime=low (round-4 links) | — | Retired; acknowledgments come from the store, focus replaces sel; review links updated |
notifications.html?channel=, alert-rules.html?rule=, incident.html?id=&step=, sync.html?case=&step= | as linked | Honoured (step only for open INC-0412) |
What’s next
- Review visually: open every link in the product map in both themes and at phone and tablet widths, and record defects.
- Decide the round-5 open questions: the enroll and queue exit codes, “resolved after 5 min stable”, the 10.0.30.5 fixture endpoint, network draft clean-up, and the 7-day custom-range limit.
- Fix what review finds in a further checkpoint.
- Prepare the final handoff only after you accept the design. This package is a nonfinal complete-design review checkpoint, not a handoff.