Demo script
The same walkthrough every time, so the talking is about the product rather than about where to click.
Today inside the app is always 28 September 2026: every "45 days" and "47 days overdue" below counts from that date. "Expiring" means within 90 days.
Reset at the END of a run, not the start: Reset demo data, lower right
of the /hub Demo tab, or in the corner of any client demo card. The demo is
then ready for whoever opens it next.
The six logins
The persona rail (the View as tab on the left edge of every screen) switches
between them without losing the screen you are on. One password for all six
(NEXT_PUBLIC_DEMO_PASSWORD); the hub's tiles sign in for you.
| Who | Level | Scope | |
|---|---|---|---|
| Daniel Sgalia | Driver | daniel.sgalia@gmail.com |
His own file, his truck, his route |
| Kyle Fredette | Supervisor | kyle.fredette@example.com |
Rutland |
| Paul Marker | GM | paul.mark@example.com |
Six sites |
| Sal Hankins (VP, Safety) | VP | sal.weston@example.com |
All ten sites |
| Russ Delaney | Supervisor (rail: Maintenance) | russ.delaney@example.com |
Rutland: the shop |
| Devon Braithwaite | Driver level (rail: Mechanic) | devon.braithwaite@example.com |
His own repairs; reports to Russ |
The front door, and getting around
The launcher card (the Demo tab of /hub, and the whole of a client's /hub)
has two buttons: SCOPE and COFI. SCOPE opens the persona chooser, where
every act below starts. COFI opens the operations report on the same data,
and asks for its own sign-in. It is on every build now, Novvia's included.
Two kinds of navigation, and they look different on purpose. The thin dark strip across the very top of every screen is the DEMO's: Hub, and nothing else. It is not part of the product and a real deployment turns it off. Inside the app, each screen's own header carries the PRODUCT's way home: Dashboard, or Home for a driver. For the screen before this one, use the browser's Back. A record opened from a dashboard table opens in a NEW tab: close it, or use its Dashboard link, to carry on. A client card's names come with it.
On every supervisor, GM and VP dashboard, Reports leads the button row (outlined, with a rule between it and the capture buttons): one screen, five record types (qualification files, inspections, corrective actions, observations, incidents), every column sortable, every row opening its record. The charts at the foot of the dashboard are now called Analytics.
Act 1: A driver's morning · Daniel, /home
Hub → SCOPE → Daniel Sgalia. Three panels:
- Needs your attention: 3. Professional Driver Series, 47 days overdue; DOT medical certificate, expires 12 November, 45 days out; Annual Road Test, expires in 64 days. Each carries its paperwork beside the date: the medical certificate reads ON FILE · checked by Kyle Fredette, the road test and the course read No document.
- Driver qualification file: the same three lines and "3 other lines are
current". The home screen lists only what needs him. See your whole file ›
opens
/dq/PER-0597, where all six lines are, and the three paperwork states are all on one screen: the CDL Class B (valid to 2028) reads Awaiting check · with Kyle Fredette, the medical certificate On file, the road test and the courses No document. - Hours: pre-trip 05:15 on RUT-003 · today's route RUT Residential 10 · RUT-003 · 959 stops planned · out 05:15, due off 14:15. Under it, his last 10 routes: 95h on duty · 6,336 stops · 831 miles · 9.5h a day.
Say: the 14-hour window is the law, due-off is the plan, and the screen keeps them apart. He sees no other driver, no investigation, no cost: that is row-level security in the database, not a filter in the page.
Say, on "Needs your attention": it is three because anything inside 90 days is on it, not only what has already lapsed. The road test is two months out and he is being told now, while there is time to book it.
Say, on "No document": that is the honest state of this client's file today. 1,217 qualifications and, before this, nothing behind any of them. The dashboard counts it: Leading indicators → DQ file documented.
The card goes up, and somebody checks it
The one loop in the product that a driver closes himself. It starts on the medical certificate line, which already says who asked and by when: Asked for by Kyle Fredette · due 29 September, tomorrow.
- Replace it on that line, then add a photo of the new card. Camera and file are separate buttons here, not one picker: he is standing at the truck. Fill in the number and the expiry off the card; the expiry is defaulted from how often the requirement renews and he can change it, because 49 CFR 391.43 leaves the term to the examiner.
- Send for approval. The line changes under his hand: Awaiting check · with Kyle Fredette, and the instruction stops telling him to schedule an exam he has already been to. Nothing on his record has moved yet.
- Switch to Kyle. His Tasks panel carries Document to check · DOT Medical Examiner's Certificate · Daniel Sgalia. The row opens Daniel's record at Qualifications; Check it opens the form already holding what Daniel typed.
- Sign it off. The qualification is created naming Kyle, the old card drops under Replaced, the ask closes itself, and Daniel's line goes Valid · On file. His "Needs your attention" count drops from 3 to 2, and the medical certificate leaves his home screen's file panel for the whole file.
Say: nobody signs their own document, and the database refuses it rather than the screen hiding the button. And the record was not created when the photograph arrived: it was created when somebody took responsibility for it.
Act 2: The DVIR refuses a truck · Daniel, /dvir
- Type transfer in the picker. Six Transfer Tractors come back ⚠ needs CDL Class A + Transfer Tractor-Trailer Operator, listed, not hidden: RUT-009, 016, 062, 065, 068, 074.
- Tap RUT-009. The checklist is replaced by the reason: CDL Class A, not on your record · 49 CFR 383, and the evidence: On your file: CDL Class B, valid to 31 Mar 2028. No signature control, no odometer, no checklist.
- Tap Open my qualification file → his whole DQ file,
/dq/PER-0597: CDL Class B, valid, license N88214703, NH DMV. (His home screen's panel now lists only what needs him, so the valid CDL is not on it; the file page shows every line.) Point at what is not there: no Class A line anywhere. That absence is the refusal. The browser's Back returns to the DVIR, RUT-009 still selected and the refusal still showing. - The second level (Paul, 2026-10-05). Type front. Rutland's nine Front Load Trucks come back ⚠ needs Front Load Truck Operator: straight trucks his Class B covers, which the company has not certified him on. Tap RUT-007: the refusal names the company cert, and cites no regulation, because it is the company's rule rather than the DOT's. Side and rear loaders and roll-offs are open to him: he holds those three certs.
- Clear the search, pick RUT-003, his own route truck this morning. Full checklist returns. Fill it, sign it, file it.
Between rehearsals: a truck or Pre/Post choice left over from the last run is cleared with Start over, top right of the DVIR header. It asks first only when answers or a signature would be lost.
Say: the block is a read of his qualification file, not a rule typed into the app: change the file and the trucks change, with nothing redeployed. Two levels: the license says he may drive a class of truck, the company cert says we have trained him on this one. And lapsed and never-held are worded differently on purpose: one is a renewal, the other is a different job.
Optional, the coverage check (the reactive half). As Kyle, /reports →
Equipment coverage. The strip reads every machine at once: people who can
operate today against the machines in scope, red where there are fewer people
than machines. Site Rutland Central, License CDL Class B, tap the
Automated Side Load Truck tile: everyone in Rutland with a Class B and the
side-load cert. Tap Roll Off Truck: the same question for roll-off. Say:
this checks who COULD cover a machine, never who is rostered to it; a Class A
counts wherever a Class B is asked for, exactly as it does at the truck.
Optional, the exception. As Kyle, /dashboard → Daniel on the
watchlist → release RUT-016 (another of the six Transfer Tractors) with a reason ("Yard move only, wash bay to
transfer bay, not on public roads"). Back as Daniel: RUT-016 shows a tick and an
amber banner naming Kyle. Say: one person, one vehicle, one day, never
backdated, and it confers no legal authority to operate.
Where the defect lands · Russ, then Devon, then Russ
Daniel's pre-trip on 25 Sep (INS-233244, RUT-003) wrote up Brakes, Major: pushrod stroke at the adjustment limit. Filing a DVIR with a defect RAISES THE REPAIR by itself and routes it to the site's maintenance supervisor; it is not on Daniel's list, because a driver does not change brakes.
- Rail → Maintenance (Russ Delaney). His screen is the shop's: DVIR and Report incident in the header, no KPI strip, no people. Tasks: the brake job reads Maintenance · Maintenance Repair - brakes · Due 29 Sep · Assign, and three repairs his mechanics have marked done read To sign off. Under it the Shop queue, every repair with its stage chip: New (not handed out), Assigned, Awaiting sign-off.
- Assign → the repair opens with where it came from (vehicle inspection, INS-233244 · Brakes (Power Unit) · major, Open DVIR, Open vehicle RUT-003) and Assign to → Devon Braithwaite, Mechanic → Assign. Assigned to Devon Braithwaite. Russ Delaney signs it off when it is done. The footer will not let Russ close it himself: it is his to hand out.
- Rail → Mechanic (Devon). His Tasks: the brake job, Maintenance · Mark Done · then Russ Delaney signs off. Press Mark Done. The row STAYS, grayed: Work marked done · waiting for Russ Delaney to sign off.
- Rail → Maintenance. The job is now To sign off · Maintenance Repair - brakes · Devon Braithwaite → Sign Off. The repair and its task close.
- Open DVIR (or
/inspections/INS-233244): Daniel's line still says exactly what he wrote, and beside it a green CORRECTED · Signed off by Russ Delaney 28 Sep. The DVIR is never edited; the repair's status is shown next to it.
Say: the driver finds it, the system routes it to the shop, the supervisor assigns it, the mechanic does it, the supervisor signs it off, and the record says who at every step.
Optional, the hold. File a fresh DVIR as Daniel with an Out of Service defect (RUT-003 is his route truck). The truck goes Out of Service the moment it is filed and the repair lands on Russ at Critical, due today. Take it through steps 2 to 4 and the truck is back In Service at the sign-off, its downtime clock stopped. Reset afterwards.
Act 3: Something happens · Daniel, /incident
Pick the kind (Someone was hurt / Vehicle or property / Near miss), who it happened to, a one-line summary and what happened, add a photo (Take a photo and Choose from photos are separate buttons, he is standing at the scene), then Where and Severity (it starts at Minor, so a near miss is quick to file): Submit stays gray until Where is set. Confirmation: saved, status New, with its incident number and the photo.
Say: the record is written first, so a photo that will not upload can never be the reason a report is not filed. Intake reports; it does not assign.
Act 4: The supervisor's day · Kyle, /dashboard
- KPI strip: outcomes · follow-through. A supervisor's strip carries no rates: one site's hours are too few for a TRIR worth banding. The rates are Act 6.
- Tasks: Housekeeping Correction - container staging (ACT-D600; the panel shows the title, not the number), due 29 September, then Paul Marker signs off → Mark Done. The row stays, grayed, waiting for Paul Marker to sign off, and the confirmation names him. The action does not close: nobody has vouched for it yet.
- Tasks, one row down: Document to check · CDL Class B · Daniel Sgalia. Daniel photographed his licence to get his file complete; nothing on his record changes until Kyle opens it and signs it. If Act 1's medical card has already been done there will be one row here rather than two.
- Tasks, also there: Request for input · Training & qualification records · IN-20260924-002, due 2 October (RFI-00004, from Paul Marker; the row names neither). Paul owns IN-20260924-002 (Near miss: reversing truck and a person on foot at the fuel island) and wants Sabine Selwyn's backing and spotter training records for the last two years. Leave it open: it is the same mechanism Act 5 shows from the owner's end, and answering it here gives that away early.
- Watchlist → People → drill into a name → the evidence behind every signal.
/observation: pick an employee, mark a behavior Unsafe, then tick Assign remedial training. The courses that answer that behavior are listed first, marked ★ (13 of 85 courses answer Driving Behavior). Untick the behavior and the shortlist narrows. No need to submit: leave it. Because an employee is named and a line is rated, it is kept, and back on the dashboard Tasks shows Incomplete observation ·· 1 of 21 rated , with Discard. Picking a name alone keeps nothing, so a mis-tap never leaves a half-record behind. Picking that employee again offers the unfinished one back rather than starting a second.
Act 5: The investigation · Sal, then Paul
As Sal, their Tasks panel → the Investigation row → /investigation
opens on IN-20260921-001, Driver made contact with retaining wall. The
queue itself is every open incident in their scope (80, or 81 once Act 3's report
is in), so go by the Tasks row
rather than scrolling for it. She owns it and has sent three pieces out:
| Request | Asked of | State |
|---|---|---|
| RFI-00001 | Paul Marker | Waiting on him |
| RFI-00002 | Kyle Fredette | Back for their review |
| RFI-00003 | Paul Marker | Accepted |
- Open RFI-00002: Kyle's own words, and the full trail of the request.
- Try to close the investigation. Refused: 2 requests for input are still open: RFI-00001 is still out and RFI-00002 has not been accepted. The escape is Cancel with a reason.
- Switch to Paul → the Request for input row on his Tasks panel, or
/requests. Answer RFI-00001 and watch it leave his list and land on Sal's. - Back as Sal: Accept RFI-00001 and RFI-00002. Root cause and cost are already filled in; tick the two attestations, close. The record names who signed and when.
Say: a delegate gets a brief, not the record: what happened, when, where, to whom, never the root cause, the cost or the classification. Somebody at another site can answer without anybody widening what they may see.
Act 6: The exec view · Sal, /dashboard
- Sites (opens from closed) → sort on TRIR. Castleton & Fair Haven is at the top: TRIR 20.57 YTD, on 11 recordables at a site of 68 people, more than any other site, including Rutland Central at 112. Over the full 24 months it reads TRIR 7.18, the worst of the ten. Press Castleton's name on that row (it carries ACT NOW) and Why it is here opens, including 18 incidents in the last 60 days against 6 in the first 60.
- Site filter → Castleton, then All. Everything follows it, rates included: TRIR reads 20.57, then 7.10 company-wide. The status rail lives inside the Incidents panel and narrows only that table: the tiles and the rates do not move when it does, because a narrowed numerator over unnarrowed hours is not a rate.
Say, on Castleton: this is what the site band exists to catch. It is not the biggest site and it is not the one with the most incidents. Its injuries are getting worse faster than anywhere else, and every trend here is judged against the site's own history, so a site that was fine last year still gets flagged.
- KPI strip: outcomes · rates vs industry · follow-through. Hover the
ion TRIR for the exposure basis. - Click the TRIR tile → the Incidents table narrows to Recordable (33 YTD) → Open record on a row → the incident, in a new tab.
- Analytics at the bottom: the charts, opened from closed. (It was called Reports; that name now belongs to the Reports button, which opens /reports.)
Ask is shelved as of 2026-09-21: no box, no tile, no documents. Built, tested and withdrawn from the demo, not removed from the product; one line in
packages/core/src/access.tsbrings it back.
Reset
Reset demo data → Yes, reset it. About twenty seconds (17.7s measured 2026-09-24, 330,563 records), and it reports how many records it put back. The button is lower right of the Demo tab and in the corner of every client demo card: one database sits behind them all, so either one resets everything.
Undone: the DVIR, the release, the incident report, the observation, the Mark Done, the assign, the sign-off and any truck it released, the answered request, any unfinished observation or report left on a Tasks list, and the documents filed in Act 1 -- rows and uploaded files both, so the bucket goes back with the tables. Untouched: the logins, sites, people, vehicles, the policy library, the client-questions board and the Ask log.
And the DQ file comes back seeded, because the baseline was taken over
scripts/db/seed_dq_documents.mts rather than over an empty table: Daniel's
medical certificate on file, his CDL awaiting Kyle's check, the ask for the
renewed card open and due 29 September, tomorrow. Re-seeding by hand is only needed after a
load.sh --reset, and the baseline has to be re-taken after it.
If something looks wrong
| Symptom | What to do |
|---|---|
| Amber "not everything is covered" | The app gained a table since the baseline. The reset still ran, so take a fresh one: select * from app.demo_snapshot(); |
| Re-baseline deliberately | Get the database how you want it, run that same line. Everything resets to that from then on. |
| The DVIR opens on a truck that does not exist (e.g. LON-039) | A draft saved on this device before the 2026-09-23 rename. Clear the vehicle box and pick again; the draft goes with it |
| Check for drift | select * from app.demo_baseline_drift();, returns nothing when all is well |
| Prove it works | psql "$SUPABASE_DB_URL" -f scripts/db/verify_demo_reset.sql |
Updated 2026-09-23 for the two-button launcher, Back and Start over. Act 2's step 3 walked in the browser as Daniel: RUT-009 refused, the file, Back, RUT-009 still selected.
Walked in the browser 2026-09-23 on production, all six acts and the reset, as the four logins; the wording above is what the screens say.
Re-pinned 2026-09-23 after the Rutland County rename (E-4/E-5), and again after the Castleton TRIR regen (f03d58a), which moved none of the rows named here: every truck, route, tractor and incident named above was read back from the reloaded database and the screens walked as Daniel and Kyle. Unchanged by the reload: Daniel's file (45 / 64 / 47 days, CDL Class B to 2028), his two documents, the ask due 29 October, ACT-D600, and RFI-00001 to 00003.
Re-pinned 2026-09-21 against the live database after the window moved to 2024-10-01 → 2026-09-28. Every row named above was read back from the database rather than carried over, because an incident number encodes its own date and an RFI number is a sequence: both move whenever the data set is regenerated.
The rows that did NOT move are the ones gen_demo_user.py now expresses as
offsets from the anchor -- the med card at TODAY+45, the course 47 days overdue,
the pre-trip at 05:15 today. Those are stable by construction. If you re-anchor
again, re-read this file's named rows from the database; scripts/db/verify.sql
prints most of them.