- Handle loading, unauthenticated, permission-denied, and unavailable states truthfully in metric-card-lean (Peak Lean Angle KPI) rather than falling back to '--' and 'No IMU roll data'
- Reset metrics state to null on auth change and in onStatusChange loading/error branches to prevent stale metrics retention
- Add race condition guard in AppCloudData.loadData to discard in-flight results if firebaseAuth.currentUser changed during asynchronous fetch
- Prioritize Disconnected and Connecting states over sample example messages in real club mode in founder-chat.tsx
- Clear chat messages on channel switch in founder-chat.tsx to eliminate cross-channel message bleed, and fix invalid py-0.2 Tailwind classes to py-0.5
- Add regression tests in account-metrics-truthfulness.test.tsx and founder-routes-chat.test.tsx
- Replace false zero metrics (0 miles / 0 rides / 100% Cloud Synced) on loading, unauthenticated, permission-denied, or network errors with truthful states
- Update AppCloudData to query motorcycles and rides via Promise.allSettled and report granular CloudDataStatus
- Update AccountPage KPI cards to display Loading…, —, or Unavailable with explicit contextual status and error recovery
- Fix founder demo chat to permanently preserve Example thread and example badges during preview=demo simulation instead of flipping to Live Connected
- Add 8 unit/component tests in account-metrics-truthfulness.test.tsx and 6 tests in founder-routes-chat.test.tsx
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>
The account page asserted several things that were not backed by real state:
- Two-Factor Authentication was hardcoded to "ENABLED" for every signed-in user.
Now derives from the real Firebase multiFactor enrollment and shows "NOT ENABLED"
(enroll in the mobile app) unless a factor is actually enrolled.
- "App Connections & Sync" showed a hardcoded connected iOS app (iPhone 16 Pro, v4.1.0,
"14m ago") and a paired "Cardo Packtalk Edge". Replaced with truthful states: real
Google account link, authenticated-device count from the real /api/account/sessions
data, and an honest "Unavailable on web" for Bluetooth comms/accessories (a browser
cannot observe hardware pairing). Header status reflects real sign-in state.
- Dashboard metrics initialised to fabricated totals (12,480 mi / 184 rides / 56.4 deg).
Now initialise to zero; AppCloudData replaces them with the rider's real ride totals.
- Dead controls fixed: "Manage Devices" and "Review active sessions" now anchor the real
Active Device Sessions panel; the telemetry-privacy control no longer presents an
unverified setting with a no-op "Change" button (managed in the mobile app).
Also adds the Phase 1 website feature audit matrix (docs/).
Tests: account-truthful-states asserts no fabricated device/pairing/sync claims render
and that 2FA is never shown as ENABLED without a real enrolled factor.
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>
* feat(clubs): enable admin group ride governance
* feat(clubs): add secure member ride discovery
* fix(clubs): protect meeting location in ride discovery
* feat(group-rides): secure club join moderation
* test(clubs): update ride discovery expectations
* test(founder-portal): update rides governance expectations
* fix(group-rides): resolve pre-merge findings on join moderation and discovery
- Block participant hard deletes for accepted status and organizer role for all callers
- Support atomic accessUids removal on invitation decline in firestore.rules
- Restrict mutual-block checks to approvals in GroupRideService, allowing decline of blocked requests
- Implement cursor-paginated candidate scanning with block filtering in website discovery API
- Add comprehensive emulator, vitest, and unit tests covering all 5 findings
* fix(discovery): page candidates until 30 eligible rides collected or query exhausted
- Remove artificial 150 candidate scan ceiling in club rides discovery route
- Continue cursor pagination until 30 unblocked eligible rides are collected or upcoming schedule query is exhausted
- Add test verifying pagination continues past 150 blocked rides to collect all 30 eligible rides
---------
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.
- Gate ranks-roles management route on canManageSettings with safe
unauthorized state, matching sibling founder-management routes
- Enforce HTTPS image URL invariant for club logo/banner in the data
layer (validateClubImageUrl) and in Firestore rules, preserving
null/empty clearing