The replay loop advanced the cursor by one sample every 50 ms / speed, assuming a uniform
20 Hz stream. Recorded telemetry is not always uniform (segment breaks and GPS-loss gaps up
to 5 s), so the rider marker drifted out of sync with the elapsed-time readout — which is
derived from real timestamps — and "1x" did not match real time across gaps.
Make playback timestamp-driven:
- Add advancePlaybackIndex(points, fromIndex, budgetMs): a pure helper that consumes a
budget of virtual recorded time, advancing by the real delta between samples so the
marker tracks the elapsed-time clock. Disconnected transitions (per
areTelemetryPointsConnected) are compressed to a single 50 ms step so playback never
stalls across multi-second dead time. No coordinates are altered.
- Drive the rAF loop with frameDelta * playbackSpeed plus carry-over, resuming from an
index ref so manual scrubs are honored.
Verified sound without changes: SHA-256-checked R2/inline telemetry load, CSV/GPX parse and
export integrity, loading/error/empty overlays, and tile-provider fallback.
Tests: advancePlaybackIndex covers uniform cadence, carry-over, gap compression, end-clamp,
and empty input. Existing replay/export/corner tests remain green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Support page was a single static line plus a mailto link. There is no public
ticket backend (only the internal ops console), so rather than simulate ticket
submission, improve the page truthfully:
- Add an FAQ grounded in features that actually ship (offline recording/sync,
billing + 2FA managed in the mobile app, GPX/CSV export, session revocation,
radar live-presence rules, public vs authenticated group rides).
- Add a validated contact form (name/email/topic/message) that opens a prefilled
email in the visitor's own client. It never claims a ticket was filed and states
plainly that the message is not sent until the visitor sends it.
- Keep support@motorsignal.app as the real, working fallback channel.
- Preserve the existing public-page visual system (content-page / eyebrow / faq-section).
Tests: FAQ + real mailto render; invalid input shows validation errors and no
submission claim; valid input opens a mailto (no simulated ticket) with the topic
and email encoded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a club has no curated routes or a chat channel has no messages, the data layer
returned fixture content with no indication it was sample data, so riders could mistake
MotorSignal's "Coastal Marshes" route or canned chat messages for their club's real data.
Flag fixture-sourced content and label it in the UI:
- Add isExample to ClubRoute and ChatMessage; set it on every fixture fallback path
(empty Firestore collection/channel and the dev demo preview).
- Founder Routes: amber "Examples" count, an explanatory banner, and a per-card "Example"
badge when the routes shown are samples.
- Founder Chat: an "Example thread" pill (instead of "Live Connected") and a banner when
the only messages are examples, so the header never claims a live conversation that
does not exist.
Tests: example-labelled routes render the banner/badge/count; example-labelled chat
renders the banner and "Example thread" pill and does not claim "Live Connected".
Existing route/chat tests (real data, no isExample) remain green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Ranks & Roles "Operational Permission Matrix" previously waited ~400ms and
showed a "Saved" state without persisting anything. No Firestore document, API
route, or security rule stores or enforces a per-rank capability matrix; club
authorization is role-based (Owner/Admin via getPortalPermissions). Persisting
the matrix would require frozen security-rule changes and would create an
unenforced permission model that misleads owners.
Make the control truthful instead of faking success:
- Render the matrix as a read-only reference of the standard governance model
by default (persistenceAvailable=false): no Save/Reset controls, disabled
toggles, a "View-only reference" indicator, and a notice stating that access
is enforced by Owner/Admin roles and per-rank customization is planned.
- Remove the fake setTimeout save handler and the unconditional success state.
- Retain a forward-compatible editable path behind persistenceAvailable for
when a backend contract ships.
- Document the precise backend contract required to enable persistence.
Tests: read-only matrix renders no Save control, reports no save, and does not
mutate permissions even for Owner/Admin; editable path still fires toggle/save
/reset when persistenceAvailable=true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fix RenderFlex overflow at short landscape breakpoint (640x320) by scaling center controls in ResponsiveDashboardLayout and clamping LeanAngleIndicator bounds
* feat(ops): add truthful disabled action plane and support modal
* fix(ops): align support report types with backend contract
* fix(ops): address action audit and support validation review
* test(website): add behavioral coverage for support reports
---------
Co-authored-by: Developer <dev@example.com>
* feat(ops): implement production operations infrastructure dashboard (phase 1)
- Add backend read-only infrastructure metrics collector (CPU, RAM, swap, disk with 80%/90% thresholds, uptime, network cumulative totals, systemd service allowlist) using standard library only (no psutil).
- Register GET /api/v1/ops/infrastructure endpoint guarded by require_ops_roles('operator', 'owner').
- Add Next.js API proxy route /api/ops/infrastructure validating ops session cookie and forwarding to backend.
- Implement dark-surface OperationsInfrastructure component in website admin console with 30s visibility-gated polling and overlap prevention.
- Add comprehensive backend and frontend test suites (8 backend tests, 43 ops-experience tests).
* fix(ops): query internal website version endpoint defensively on verified port 3001
- Query internal website GET /api/version on verified production listener http://127.0.0.1:3001 (configurable via WEBSITE_INTERNAL_URL).
- Parse JSON defensively with a 4096-byte response bound and validate exact 40-char hex revision pattern.
- Add comprehensive tests for valid, missing, malformed, oversized, and unreachable version responses.
- Do not expose internal URLs or connection errors in API responses.
* fix(ops): map postgresql service to cluster unit postgresql@14-main and preserve health errors
- Map public logical service ID 'postgresql' to fixed systemd unit 'postgresql@14-main'.
- Reject direct systemd unit names and arbitrary service names.
- Preserve HTTP 5xx responses as degraded on /api/health before root fallback.
- Add regression tests for postgresql unit mapping, API response name stability, and health 5xx preservation.
---------
Co-authored-by: Developer <dev@example.com>
* fix(website): expand Founder Portal layout width to fill available canvas
* fix(website): restore mobile padding, preserve public main landmark, and harden width test
Apply responsive mobile padding to .site-page-frame under 760px. Render semantic <main> for public routes and <div> for portal routes with internal <main> via SitePageFrame. Assert max-w-[1536px] exclusively and reject old max-w-[1440px] cap in founder-portal.test.cjs.
---------
Co-authored-by: Developer <dev@example.com>
Add the disabled Founder Portal Club Rides scaffold aligned with the live GroupRide.clubId domain.
Keep production discovery and actions disabled until a privacy-preserving read path and real management destinations are implemented.