Platform documentation · July 2026

One platform for keeping cars in shape.

Bluefit connects drivers, workshops, and the platform team around a single source of truth for every vehicle — mileage, maintenance schedules, service history, and real bookable slots. Three PWAs, one Cloudflare Worker, one Postgres.

bluefit.app workshop.bluefit.app admin.bluefit.app
3
Surfaces
1
Worker
1
Database
0
CORS headaches
Driver garage screen
Vehicle detail screen
01 The network

Three surfaces, one Worker

Each subdomain is the same Worker wearing a different face. Host-only session cookies give every surface an independent login, and each one installs as its own PWA with its own manifest. On localhost the surfaces fall back to /workshop and /admin paths.

bluefit.app

Driver PWA

A phone-first garage. Track mileage, see exactly when service is due, find a workshop nearby, and book a real slot in a few taps.

For car owners
workshop.bluefit.app

Workshop CRM

A day-by-bay calendar, a booking-request inbox, customer vehicles with full history, and the service catalog & capacity settings.

For service centers
admin.bluefit.app

Platform admin

Onboard service centers, manage users and locations network-wide, and watch every outbound email and audited action.

For the Bluefit team
02 bluefit.app

The driver app

Drivers live in a 430px-wide world: a garage of vehicles with at-a-glance health, per-vehicle maintenance schedules driven by mileage and time, and a booking flow that only ever offers slots the workshop can actually honour.

Garage

Garage

Every vehicle with a health ring and the next service distilled to one line. Mileage updates are one tap away.

Vehicle detail

Vehicle detail

Maintenance schedule (baseline intervals, workshop overrides), current odometer, VIN, and the full service history — workshop-logged and self-reported.

Booking flow

Book a service

Pick a workshop, service, and time. Slots come from live availability: bays × hours × existing bookings, in the location's timezone.

Bookings

Bookings

Upcoming and past appointments with live state — pending, confirmed, completed, cancelled — and one-tap cancel.

Discover

Discover

PostGIS-backed search for workshops near you — by distance or by soonest available slot for the service you need.

Privacy

Privacy & sharing

A confirmed booking shares the vehicle with that workshop. Drivers can revoke visibility at any time; past work stays with the workshop that did it.

03 workshop.bluefit.app

The workshop CRM

Workshops run their whole day here: confirm requests, see the schedule bay by bay, pull up any customer's vehicle, and log the work — which feeds straight back into the driver's maintenance schedule.

Day schedule, bay by bay

The calendar renders each bay as a column in the location's timezone, with business hours shaded and jobs placed on the real timeline.

  • Add appointment creates workshop-origin bookings — walk-ins and phone jobs live on the same grid.
  • The pending badge is a live count of requests waiting for confirmation.
  • Staff switch between locations they belong to from the sidebar.
workshop.bluefit.app
Workshop calendar

The request inbox

Consumer bookings arrive as pending holds. Confirm books the bay and emails the driver; decline frees the slot instantly.

  • Confirming also grants the workshop visibility of that vehicle — that's the entire sharing model.
  • Stale holds expire automatically (cron + lazy sweeps), so abandoned requests never block real work.
workshop.bluefit.app/requests
Booking requests

Customers & their vehicles

Every customer who has booked here, with phone numbers editable in place. Opening a vehicle shows maintenance state and history, and + Add service record logs work with mileage and notes.

  • Visibility is scoped per location — staff only see customers of their workshop.
  • Walk-ins can be created record-only and later claim their account by email.
workshop.bluefit.app/customers
Customers
workshop.bluefit.app/customers · vehicle panel
Vehicle panel

Services & capacity

The catalog (name, duration, price), the bays, and the weekly operating hours — together these define exactly which slots the driver app can offer.

  • Hours are wall-clock in the location's IANA timezone and converted per-day to real UTC instants.
  • Catalog and capacity edits are gated to location admins; day-to-day booking work is open to all staff.
workshop.bluefit.app/services
Services and capacity
04 admin.bluefit.app

Platform operations

The Bluefit team's control room: onboard service centers, manage every user and vehicle on the network, and audit everything — including every email the platform ever sent.

admin.bluefit.app
Admin overview
Network overview Live counts across the network, with locations broken down by service center.
admin.bluefit.app/centers
Service centers
Service centers Chains and their locations; "Onboard center" walks through org → location → catalog → staff in one flow.
admin.bluefit.app/users
Users
Users Every account — drivers, workshop staff, global admins — filterable by role, with edit, deactivate, and password reset.
admin.bluefit.app/locations
Locations
Locations The flat list across all centers with coordinates and setup state; each opens the full location setup screen.
admin.bluefit.app/vehicles
Vehicles
Vehicles The global registry — make, model, plate, VIN, owner — searchable across the whole platform.
admin.bluefit.app/notifications
Notification center
Notification center A send-time log of every outbound email — kind, recipient, subject, sent/failed/skipped — filterable and linked to bookings. Bodies are never stored.
admin.bluefit.app/audit
Audit log
Audit log Append-only trail of who did what to which entity — org renames, booking confirmations, password resets — with structured metadata.
05 Under the hood

Small surface, sharp invariants

The whole platform is one Hono app on Cloudflare Workers, serving the SPA and the API from the same origin, in front of one Neon Postgres. Correctness lives in the database — not in a fleet of services.

Driver PWA
bluefit.app
Workshop CRM
workshop.bluefit.app
Admin
admin.bluefit.app
Cloudflare Worker — Hono
/api/* routes + static SPA assets (Vite · React · Tailwind v4) · same origin, host-only cookies
Neon PostgreSQL · via Hyperdrive (caching off)
Drizzle ORM · PostGIS for discovery · btree_gist for the booking EXCLUDE constraint
Email Service
native binding · service.bluefit.app
Cron · every 10 min
expire holds · 24h reminders
Google OAuth
code flow + PKCE in the Worker
deploy = drizzle-kit migrate → build web → wrangler deploy · push to main does this automatically
Concurrency

The database is the booking referee

A Postgres EXCLUDE (bay_id, time_range) constraint (btree_gist) makes double-booking impossible no matter how requests race. Violations surface as "no bay available". Holds get a TTL; a cron sweep plus lazy expiry in the hot paths clean them up.

Privacy

Visibility is earned, and revocable

Workshops see a vehicle only after a confirmed booking writes a location_customers grant — in the same transaction. Drivers revoke from the Privacy screen (revokedAt); visibleVehicleIds() is the single source of truth for every read.

Authorization

Four actor kinds, two location gates

Actors are consumer, platform_admin, org_admin, or location staff. Day-to-day work (bookings, customers, records) needs canManageLocation; touching catalog, bays, or hours needs canAdministerLocation — location admin and up.

Time

Real UTC instants, local walls

Timestamps are true UTC. Each location carries an IANA timezone; operating hours are wall-clock minutes converted per-day to UTC ranges. Every UI and email renders in the location's zone — never the viewer's browser zone.

Delivery

Every email is on the record

Sends go through the Cloudflare Email binding from service.bluefit.app, and every attempt is logged to the notifications table — kind, recipient, subject, status. Metadata only; bodies (which can contain credentials) are never stored.

Operations

CI rehearses against production's schema

PRs run typecheck, web build, and the test suite; migrations are rehearsed on an ephemeral Neon branch forked from production. Merging to main deploys with the exact same npm run deploy a human would run.

06 Data model

Seventeen tables, four neighbourhoods

The schema lives in src/db/schema.ts and migrates with drizzle-kit. This is the whole map.

Identity & access

users
Drivers, staff, and admins in one table; email + PBKDF2 hash or Google sub
sessions
Hashed bearer tokens behind the httpOnly cookie
memberships
Staff roles per org/location (staff · location_admin · center admin)
account_claims
Single-use tokens for walk-in customers to claim their account
location_customers
The visibility grants — who may see which customer's vehicles

Workshops

organizations
Service-center chains
locations
Physical workshops: address, geo, phone, IANA timezone
service_types
Per-location catalog with duration and price
bays
Physical capacity — one job per bay at a time
location_hours
Weekly wall-clock opening hours per weekday

Vehicles & care

vehicles
Make, model, year, plate, VIN, owner
mileage_readings
Odometer history — manual, service visit, or OBD
maintenance_items
Baseline intervals (km/months) + workshop overrides
service_records
Work performed, by whom, at what mileage

Bookings & ops

bookings
tstzrange + bay, guarded by the EXCLUDE constraint; six states, two origins
notifications
Send-time log of every outbound email
audit_log
Append-only who-did-what with JSON metadata
07 API

Route groups under /api

Every route is mounted under /api and protected per-route with requireActor. A sampler of the surface:

/auth

  • POST/auth/signup · /auth/login · /auth/logout
  • GET/auth/me · PATCH/auth/me
  • GET/auth/google/start → /callback
  • POST/auth/claim · /auth/change-password

vehicles & maintenance

  • GET/vehicles · /vehicles/:id · …/maintenance
  • POST/vehicles/:id/mileage · …/service-records
  • POST…/maintenance/:item/override
  • PATCH/vehicles/:id (plate, VIN…)

discovery

  • GET/discovery/nearest?lat&lng
  • GET/discovery/soonest?service=…
  • GET/vehicles/:id/sharing (privacy)

booking

  • GET/locations/:id/availability
  • POST/locations/:id/bookings (hold)
  • POST/bookings/:id/confirm · /decline · /cancel · /accept · /complete · /no-show
  • GET/bookings (mine)

workshop crm

  • GET/locations/:id/bookings · /customers
  • POST/locations/:id/appointments
  • POST/locations/:id/service-types · /bays · /hours
  • PATCH/locations/:id/customers/:userId

admin

  • POST/admin/organizations · /locations · /users
  • GET/admin/vehicles · /customers · /notifications
  • GET/admin/audit-log
  • GET/health
08 Hands on

Run it yourself

Everything in this document was captured from a local wrangler dev build with seeded demo data. Recreate it in two commands.

Demo accounts

Seeded by scripts/seed-demo.mjs — one persona per surface.

AR driver@bluefit.test
Driver · bluefit.app
WM workshop@bluefit.test
Location admin · workshop.bluefit.app
PA admin@bluefit.test
Platform admin · admin.bluefit.app

Password for all three: bluefit123 · on localhost use /workshop and /admin paths

bluefit — terminal
# seed demo data (Neon test branch)
$ DATABASE_URL="<neon-test-url>" node scripts/seed-demo.mjs

# run locally — SPA + API on :8787
$ npm run build:web && npm run dev

# the loop
$ npm test          # vitest, against the Neon test branch
$ npm run typecheck
$ DATABASE_URL="<neon-prod-url>" npm run deploy
🌿 Neon branches

production serves the deployed Worker (via Hyperdrive, pooled). test is a child branch that tests may truncate — reset it any time with neonctl branches reset test.

🔁 CI/CD

PRs: typecheck + web build + tests, with migrations rehearsed on an ephemeral fork of production. Push to main auto-deploys via the same npm run deploy.

📚 Deeper docs

CLAUDE.md is the working guide; docs/superpowers/DECISIONS.md records why things are the way they are; specs and plans live in docs/superpowers/.