feat(demo): LIVE_MAPS override, UAT feedback form, AUTH fixes, demo runbook updates#118
Conversation
Tomorrow's client demo runs on an iPhone via a Pinggy HTTPS tunnel.
The demo brief asks for live Google Maps (geocoding + Route
Optimization) but simulated WhatsApp + email so a stray click can't
text a real customer mid-walkthrough.
Today DEMO_MODE is a single global flag — flipping it true to enable
the auth bypass and the messaging simulators *also* simulates Google.
There's no way to mix.
This PR adds a per-integration override for Maps only, and ships a
copy-pasteable iPhone-via-Pinggy runbook that uses it.
- lib/demo/demo-mode.ts:
- isLiveMapsForced() — reads EQUISMILE_LIVE_MAPS=true. Defaults to
false. Only the Maps client should consult this; WhatsApp +
email stay gated by isDemoMode() alone.
- setLiveMapsForced() — test override matching setDemoMode() shape.
- lib/integrations/google-maps.client.ts:
- getMode() now returns 'live' when DEMO_MODE=true AND
EQUISMILE_LIVE_MAPS=true (and credentials are present).
Defensive fallback to 'demo' if credentials are missing —
EQUISMILE_LIVE_MAPS=true with no API key still simulates rather
than failing the demo with a runtime error.
- Function name uses `isLiveMapsForced` rather than `useLiveMaps`
to avoid eslint-react-hooks/rules-of-hooks misclassifying the
`use` prefix as a React hook.
- __tests__/unit/integrations/google-maps-optimize.test.ts:
- 3 new cases — DEMO_MODE+override calls live API, DEMO_MODE
without override simulates, override-with-no-creds still
simulates. Existing 8 tests untouched.
- .env.example: EQUISMILE_LIVE_MAPS documented next to DEMO_MODE
with the explicit "real Google billing applies" warning.
- docs/DEMO_RUNBOOK.md (new): operator runbook covering pre-flight,
the .env.local block (DEMO_MODE + EQUISMILE_LIVE_MAPS + the
Pinggy-aware AUTH_URL / NEXT_PUBLIC_APP_URL), reset-and-rehearse
loop, sign-in shortcut, eight-page walkthrough script keyed off
the seed-demo fixtures (Mistral urgent / Sarah Mitchell routine /
Villeneuve→Aigle route), mobile sanity checklist at 390 px, and
a failure-modes table for the obvious gotchas.
Drive-by: __tests__/unit/offline/queue-replay.test.ts had two
TS errors after PR #65 made `sequence?: number` optional but didn't
update the call sites that pushed `req.sequence` into a
`number[]`. Loosened those two arrays to `Array<number | undefined>`
to match the type. Already passing CI on main, but the local
typecheck flagged it; fixing here as a tiny drive-by.
Verification (local, on a fresh checkout against this branch):
- npm run lint ✓
- npm run typecheck ✓
- npx prisma validate ✓ (with stub DATABASE_URL — same shape as CI)
- npm run test ✓ — 1000/1000 (was 997 baseline + 3 new)
- npm run build ✓
Mobile rehearsal at 390 px on the Pinggy tunnel is the next step
(D3 in the plan); operator runs it. Anything broken on that pass
goes into the same PR.
https://claude.ai/code/session_01WGFmhpjAsc2nq4uyw8YBvS
…form Three demo-readiness deliverables for tomorrow's iPhone-via-Pinggy client session, all within the existing PR #66 scope (no new code, no schema, no tests change). - scripts/windows/DEMO.bat: prints an "Integration status" block after loading .env so the operator can confirm at a glance whether the demo will run with live Google Maps + simulated messaging (the safe shape) or all-simulated. Specifically warns when EQUISMILE_LIVE_MAPS=true is set but GOOGLE_MAPS_API_KEY or GCP_PROJECT_ID is missing — the client falls back to the simulator silently in that case, which we want surfaced before showtime, not during. The footer also now points at docs/DEMO_RUNBOOK.md and the new UAT files. - docs/uat/UAT_VET_PERSONA.md (new): Dr. Rachel Kemp profile, matching the seeded demo-staff-rachel (founder + lead vet, rachel@equismile.example, brand-colour maroon). Covers her typical Tuesday, what she will and won't tolerate, what's in scope vs out of scope for the session, and which seeded fixtures map to her real coverage area (Mistral / Sophie Dupuis FR urgent / Sarah Mitchell routine / Villeneuve→Aigle route). Designed so the test reads as one continuous narrative rather than "I'm a generic admin pretending to be a vet." - docs/uat/UAT_FEEDBACK_REPORT.md (new): browser-readable fillable form covering all 12 demo surfaces (sign-in, dashboard, enquiries, triage, planning, route generation with live Google, route review, appointments, completed, bilingual, mobile polish at 390px, offline). Each section has tick-box pass/fail/partial + structured checks + free-form notes. Bottom-of-form has a numbered findings log with severity, a separate "lack-of-function" list (enhancements vs bugs), and an overall verdict with the "would you use this Tuesday?" gate. Form opens in any browser via GitHub markdown render or a local markdown preview. The vet fills it in during/after the session and either pastes into a GitHub PR comment, emails it, or hands it over in person. - scripts/windows/README.md: new "Client-demo flow" section documenting the .env additions for live Maps + Pinggy, with cross-references to DEMO_RUNBOOK and the two UAT files. Verification: lint, typecheck, prisma validate, test (1000/1000), build all green locally — same green state as the previous commit on this branch. https://claude.ai/code/session_01WGFmhpjAsc2nq4uyw8YBvS
Companion to docs/uat/UAT_VET_PERSONA.md and
docs/uat/UAT_FEEDBACK_REPORT.md (markdown). The vet wanted to fill
the report in the browser instead of editing markdown — this is the
direct delivery on that ask.
- public/uat-feedback.html — single self-contained static file:
- Mobile-first at 390 px (iPhone Safari is the target). 44 px
minimum tap targets, safe-area-inset-aware, brand-maroon header.
- 12 sections matching the markdown report 1:1: sign-in,
dashboard, enquiries, triage, planning, route generation (live
Google), route review, appointments, completed, bilingual,
mobile polish, offline. Each has Pass / Partial / Fail radios,
structured pass/fail checklist, and a free-form notes textarea.
- Findings log table (5 rows pre-rendered + "+ Add row" button)
with severity dropdown and repro selector.
- Lack-of-function + enhancement-ideas free-form textareas.
- Overall verdict block — "Would you use this Tuesday?" three-way,
biggest-fix, biggest-cheer, free-form summary.
- Persistence: localStorage auto-save on every input. Survives a
page refresh or accidental tab close. "Clear" button at the
bottom for a fresh start. Save status shown live ("Saved locally
✓ HH:MM:SS").
- Export: "Copy report" opens a modal with the full report rendered
as markdown (matches docs/uat/UAT_FEEDBACK_REPORT.md shape) ready
to paste into a GitHub PR comment, email, or Slack DM.
"Download .md" saves it as a file.
- No build step. No JS framework. No external dependencies. Static
HTML+CSS+inline JS. Next.js serves anything in public/ directly,
so it's reachable at:
http://localhost:3000/uat-feedback.html (laptop)
https://<tunnel>.pinggy.io/uat-feedback.html (iPhone via Pinggy)
The page does not require auth — it's a feedback form, not part
of the operational app, so the vet can hit it without going
through demo sign-in first.
- No code/server/test changes. Static asset only.
Verification: lint + typecheck still green.
https://claude.ai/code/session_01WGFmhpjAsc2nq4uyw8YBvS
Two papercuts caught during the live rehearsal of tomorrow's iPhone
demo. Both are demo-only (production unaffected).
- scripts/windows/DEMO.bat:
- Sets a deterministic demo-only AUTH_SECRET if .env doesn't
provide one. Without this, Auth.js v5 returns 500 on every
/api/auth/session call and the dashboard never loads after
sign-in. The default is intentionally non-secret and the value
starts with "demo-" so the integration-status block at startup
can flag it ("AUTH_SECRET = demo fallback") versus a real .env
value ("AUTH_SECRET = from .env"), versus the failure mode
where neither is set ("[WARNING] not set - Auth.js will 500").
- Same shape as the existing DATABASE_URL fallback above it. .env
still wins because it's loaded after these defaults.
- docs/DEMO_RUNBOOK.md:
- New §4a "First-time browser setup" — explains the symptom an
operator hits when the browser has previously cached an older
EquiSmile build (PWA service worker replays the old login page
with the production CSP, which includes upgrade-insecure-
requests, which silently rewrites the form POST to HTTPS,
which then doesn't match the HTTP-only allow-list in the
current CSP). Step-by-step fix for both Chrome/Edge DevTools
and iPhone Safari → Website Data.
- New §4b "Auth.js 500 on /api/auth/session" — the AUTH_SECRET
diagnostic, with the integration-status string the operator
will see in DEMO.bat at startup.
- Two new rows in §8 "Failure modes" table, cross-referencing
the new sections, so the operator can find the fix from
either entry point.
Verification: lint + typecheck green. No code change, no test
change. Bat-file change is local to DEMO.bat only; LAUNCH.bat,
production deploys, and CI all unchanged.
https://claude.ai/code/session_01WGFmhpjAsc2nq4uyw8YBvS
A fresh demo run without AUTH_URL set in .env hits this every time: [auth][error] UntrustedHost: Host must be trusted. URL was: http://localhost:3000/api/auth/session [auth][error] UntrustedHost: Host must be trusted. URL was: http://0.0.0.0:3000/api/auth/session Two distinct host strings show up because DEMO.bat binds the Next server with HOSTNAME=0.0.0.0 (correct, the Pinggy tunnel needs it), so Next's own SSR fetches use 0.0.0.0:3000 while the browser hits localhost:3000. Auth.js v5 rejects both unless trustHost is on. Today the gate was `trustHost: Boolean(process.env.AUTH_URL)` — correct for production behind a known proxy, but breaks the demo shape where neither AUTH_URL nor a known proxy host is set, AND where the bind hostname differs from the browser hostname. - auth.ts: trustHost now ORs with `DEMO_MODE === 'true'`. Demo mode unconditionally trusts whatever Host header arrives. The trust boundary is already loosened by DEMO_MODE elsewhere (CSP relaxed in lib/security/headers.ts, form-action allow-list, /api/demo/ sign-in bypass), so this is consistent. Production / non-demo deploys still require explicit AUTH_URL — unchanged behaviour. - scripts/windows/DEMO.bat: belt-and-braces — sets a fallback AUTH_URL=http://localhost:3000 alongside the existing AUTH_SECRET fallback. .env still wins because it's loaded after these defaults. Auth.js callbacks construct absolute redirect URLs from AUTH_URL, so providing it explicitly avoids any guesswork even with the trustHost loosening above. Verification: lint, typecheck, test (1000/1000) all green. No production code path changed — the OR with `DEMO_MODE === 'true'` short-circuits to the existing `Boolean(process.env.AUTH_URL)` when DEMO_MODE is unset. Closes the second blocker hit during today's iPhone-via-Pinggy rehearsal. First blocker (CSP form-action via stale SW cache) was addressed in 19836d6. https://claude.ai/code/session_01WGFmhpjAsc2nq4uyw8YBvS
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR improves the demo/UAT experience by allowing live Google Maps usage during demo mode (while keeping messaging simulated), tightening Auth.js demo stability, and adding operator/UAT documentation + a browser-fillable feedback form.
Changes:
- Add
EQUISMILE_LIVE_MAPSoverride to run live Google geocoding/route optimization even whenDEMO_MODE=true. - Fix demo-mode Auth.js host-trust behavior and improve Windows demo script output (integration-status, AUTH fallbacks, Pinggy guidance).
- Add UAT assets: persona + feedback report templates, plus a standalone
/uat-feedback.htmlform and demo runbook.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/windows/README.md | Adds a Windows client-demo + UAT flow section and links to runbooks/persona/forms. |
| scripts/windows/DEMO.bat | Adds demo-safe AUTH defaults and prints an “Integration status” block for operators. |
| public/uat-feedback.html | Adds a browser-fillable UAT feedback form with local autosave + markdown export. |
| lib/integrations/google-maps.client.ts | Allows live Google Maps calls in demo mode when EQUISMILE_LIVE_MAPS=true. |
| lib/demo/demo-mode.ts | Adds isLiveMapsForced + test override setter for per-integration demo overrides. |
| docs/uat/UAT_VET_PERSONA.md | Adds a UAT testing persona aligned to seeded demo data. |
| docs/uat/UAT_FEEDBACK_REPORT.md | Adds a markdown UAT checklist/report template aligned to the demo flow. |
| docs/DEMO_RUNBOOK.md | Adds an operator runbook for iPhone/Pinggy demos with live Maps + simulated messaging. |
| auth.ts | Trusts host in demo mode to prevent Auth.js UntrustedHost 500s during demos. |
| .env.example | Documents EQUISMILE_LIVE_MAPS override and billing warning. |
| tests/unit/offline/queue-replay.test.ts | Adjusts typings to reflect sequence possibly being undefined. |
| tests/unit/integrations/google-maps-optimize.test.ts | Adds test coverage for live-maps override behavior in demo mode. |
| .verdict input[type="radio"] { display: none; } | ||
| .verdict input[type="radio"]:checked + span.pass { background: var(--pass); color: white; padding: .35rem .75rem; border-radius: 4px; } | ||
| .verdict input[type="radio"]:checked + span.partial { background: var(--partial); color: white; padding: .35rem .75rem; border-radius: 4px; } | ||
| .verdict input[type="radio"]:checked + span.fail { background: var(--fail); color: white; padding: .35rem .75rem; border-radius: 4px; } |
| the same shape as `useLiveMaps()` — keep them per-integration so | ||
| "live everything" is never a single foot-gun env var. |
| el.addEventListener('input', () => { | ||
| let v; | ||
| if (el.type === 'checkbox') v = el.checked; | ||
| else if (el.type === 'radio') { if (el.checked) v = el.value; else return; } | ||
| else v = el.value; | ||
| setByKey(state, key, v); | ||
| save(); | ||
| }); | ||
| el.addEventListener('change', () => { | ||
| let v; | ||
| if (el.type === 'checkbox') v = el.checked; | ||
| else if (el.type === 'radio') { if (el.checked) v = el.value; else return; } | ||
| else v = el.value; | ||
| setByKey(state, key, v); | ||
| save(); | ||
| }); |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Exported
setLiveMapsForcedis never imported or called- Removed the unused
setLiveMapsForcedexport so the module no longer carries dead API surface.
- Removed the unused
- ✅ Fixed: Case-sensitivity mismatch between status display and runtime
isLiveMapsForced()now treatsEQUISMILE_LIVE_MAPSas true case-insensitively, matchingDEMO.bat’s/icheck.
Or push these changes by commenting:
@cursor push 1ae6525d46
Preview (1ae6525d46)
diff --git a/lib/demo/demo-mode.ts b/lib/demo/demo-mode.ts
--- a/lib/demo/demo-mode.ts
+++ b/lib/demo/demo-mode.ts
@@ -39,18 +39,12 @@
*/
export function isLiveMapsForced(): boolean {
if (_isLiveMapsForced === null) {
- _isLiveMapsForced = process.env.EQUISMILE_LIVE_MAPS === 'true';
+ const raw = process.env.EQUISMILE_LIVE_MAPS;
+ _isLiveMapsForced = raw !== undefined && raw.toLowerCase() === 'true';
}
return _isLiveMapsForced;
}
-/**
- * Override for testing. Call with `undefined` to reset to env check.
- */
-export function setLiveMapsForced(value: boolean | undefined): void {
- _isLiveMapsForced = value ?? null;
-}
-
// ---------------------------------------------------------------------------
// Logging helper
// ---------------------------------------------------------------------------You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 2c1f127. Configure here.
| */ | ||
| export function setLiveMapsForced(value: boolean | undefined): void { | ||
| _isLiveMapsForced = value ?? null; | ||
| } |
There was a problem hiding this comment.
Exported setLiveMapsForced is never imported or called
Low Severity
setLiveMapsForced is exported but never imported or called anywhere in the codebase. While it mirrors the existing setDemoMode pattern (which IS used in integration tests), a grep for its name returns only the definition — making it dead code at this point.
Reviewed by Cursor Bugbot for commit 2c1f127. Configure here.
| echo. | ||
| echo Integration status: | ||
| echo DEMO_MODE = %DEMO_MODE% | ||
| if /i "%EQUISMILE_LIVE_MAPS%"=="true" ( |
There was a problem hiding this comment.
Case-sensitivity mismatch between status display and runtime
Medium Severity
The DEMO.bat integration status check uses /i (case-insensitive) to evaluate EQUISMILE_LIVE_MAPS, but the Node.js runtime in isLiveMapsForced() uses strict === 'true' (case-sensitive). If an operator sets EQUISMILE_LIVE_MAPS=True or TRUE in .env, the batch output will report "Google Maps = LIVE" while the app actually uses the simulator. This silently misleads the operator during pre-demo verification — the exact scenario the status block exists to prevent.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2c1f127. Configure here.
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: Richard Knapp <RJK134@users.noreply.github.com>
Agent-Logs-Url: https://github.com/RJK134/EquiSmile/sessions/e6b49b31-b24c-4fa8-9203-7768350a1cbe Co-authored-by: RJK134 <167345619+RJK134@users.noreply.github.com>
Implemented the review-thread fixes in
Screenshot of the updated mobile UAT feedback controls: Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
…UTH fixes, demo runbook # Conflicts: # auth.ts # docs/DEMO_RUNBOOK.md # lib/demo/demo-mode.ts # lib/integrations/google-maps.client.ts Co-authored-by: RJK134 <167345619+RJK134@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
|
@copilot resolve the merge conflicts in this pull request |
Applied in
Screenshot: |
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: RJK134 <167345619+RJK134@users.noreply.github.com>
PR Review Sweep — Status & Actions TakenPerformed an end-to-end review across all 7 open PRs (#85, #116, #117, #118, #119, #120, #121). Root cause of all Vercel preview failuresEvery failing Vercel deployment fails at the Provisioning Integrations step:
The Neon Postgres branch limit is hit, so no preview can build. The CI gate (lint/typecheck/prisma/test/build) is green on all PRs; the Vercel red X is purely a Neon plan/branch issue. Action required (operator-only): prune unused Neon branches or upgrade the plan. Per-PR status
Left for you
|
|
@copilot apply changes based on the comments in this thread |
1 similar comment
|
@copilot apply changes based on the comments in this thread |
|
@copilot apply changes based on the comments in this thread |
1 similar comment
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/RJK134/EquiSmile/sessions/ad5675e6-8c63-4aaa-b905-195fcdf56437 Co-authored-by: RJK134 <167345619+RJK134@users.noreply.github.com>
Head branch was pushed to by a user without write access
Implemented in Applied the review-thread fixes in Screenshot:
Implemented in Applied the review-thread fixes in Screenshot: Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Implemented in This review thread’s requested fixes are in place in Screenshot:
Implemented in This review thread’s requested fixes are in place in Screenshot: |
|
Could not push Autofix changes. The PR branch has conflicting changes. |






Summary
Bundles 5 demo/UAT commits from
feature/demo-live-maps-overrideinto main.Scope
.env.example— addsEQUISMILE_LIVE_MAPS=falseoverride flag to enable real Google Maps API in demo mode/uat-feedback.html(browser-fillable)DEMO.bat+ UAT persona verificationfix(demo): AUTH_SECRET fallback + service-worker cache caveat in runbookfix(demo): trust host in DEMO_MODE so Auth.js stops 500-ing on/api/auth/*This branch is 58 commits behind main and has merge conflicts that must be resolved before merging. Rebase onto main and resolve conflicts before marking ready for review.
Test plan
npm run lintcleannpm run typecheckcleannpx prisma validatepassesnpm run test— all suites greenEQUISMILE_LIVE_MAPS=falseand=true/uat-feedback.htmlloads and submits correctlyNote
Medium Risk
Touches authentication host-trust behavior and changes demo-mode integration gating to allow live Google API calls, which could affect sign-in availability and inadvertently incur external API/billing if misconfigured.
Overview
Adds a per-integration demo-mode escape hatch (
EQUISMILE_LIVE_MAPS) so Google Maps geocoding/route optimization can run live even whenDEMO_MODE=true, with updated unit coverage to ensure it still falls back to simulators when unset or missing credentials.Hardens demo auth startup by trusting hosts in
DEMO_MODE(avoiding Auth.jsUntrustedHost500s) and updatingDEMO.batto set demo-safe defaults (AUTH_SECRET,AUTH_URL) plus an at-a-glance integration status readout.Introduces operator/UAT collateral: a new
docs/DEMO_RUNBOOK.md, UAT persona + feedback templates, and a browser-fillable/uat-feedback.htmlthat stores locally and exports a markdown report;.env.exampleis updated to document the new override flag.Reviewed by Cursor Bugbot for commit 2c1f127. Configure here.