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.
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.
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.
Workshop CRM
A day-by-bay calendar, a booking-request inbox, customer vehicles with full history, and the service catalog & capacity settings.
Platform admin
Onboard service centers, manage users and locations network-wide, and watch every outbound email and audited action.
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
Every vehicle with a health ring and the next service distilled to one line. Mileage updates are one tap away.
Vehicle detail
Maintenance schedule (baseline intervals, workshop overrides), current odometer, VIN, and the full service history — workshop-logged and self-reported.
Book a service
Pick a workshop, service, and time. Slots come from live availability: bays × hours × existing bookings, in the location's timezone.
Bookings
Upcoming and past appointments with live state — pending, confirmed, completed, cancelled — and one-tap cancel.
Discover
PostGIS-backed search for workshops near you — by distance or by soonest available slot for the service you need.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
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.
Driver · bluefit.app
Location admin · workshop.bluefit.app
Platform admin · admin.bluefit.app
Password for all three: bluefit123 · on localhost use /workshop and /admin paths
# 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/.