Go Happy Cab

Real-time scheduling & SMS for special needs child transportation · Marin County, CA
v1.0.0 11 commits · 2 weeks +5,283 / −1,160 lines 40 tables · ~280 functions ~120 children · ~67 drivers

Project Identity

Go Happy Cab is a production dispatch system for a special needs child transportation company serving Marin County. The dispatcher uses a web dashboard (Expo Router + React Native Web) to pair ~120 children with ~67 drivers daily across AM pickup and PM retrieval periods. The backend is Convex (real-time, 40 tables, ~280 exported functions). SMS communication with drivers is bilingual (English/Portuguese-BR) via Twilio, including group threading. The system is actively shipping features — stakeholder demos happening monthly, production data flowing, carrier approval for SMS pending.

Convex · Expo Router · React Native Web · Twilio · Clerk Auth · Google Sheets MCP

Architecture Snapshot

graph TB
  subgraph DISPATCH["Dispatch App (Expo Router · Web)"]
    DAILY["Daily View
Drag-drop pairing · Day Off toggle"] WEEKLY["Weekly View
5-column grid · Search/filter"] CRM["CRM
Drivers · Children · Schools · Clients"] SMS_UI["SMS
Inbox · Send · Groups · Chat Windows"] REPORTS["Reports
Roster · Mixed Weeks · Payroll"] end subgraph CONVEX["Convex Backend (40 tables · ~280 functions)"] ASSIGN["assignments.ts
Routes · Unassigned · Copy · Day Off check"] DRIVERS["drivers.ts
CRUD · On Hold · Day Off toggle"] SCHEMA["schema.ts
Master schema: routes · drivers · children
driverDayOffs · schools · smsMessages"] CRON["schedulePreload.ts
Sunday night full-week cron"] SHEET["sheetSync.ts
Google Sheets ↔ Convex"] SMS_BE["sms*.ts
Messages · Conversations · Groups · Templates"] end subgraph EXTERNAL["External Services"] TWILIO["Twilio
SMS · Conversations API · Webhooks"] CLERK["Clerk
Auth · Driver accounts"] GSHEETS["Google Sheets
Master driver compensation"] end DAILY --> ASSIGN WEEKLY --> ASSIGN CRM --> DRIVERS SMS_UI --> SMS_BE REPORTS --> ASSIGN ASSIGN --> SCHEMA DRIVERS --> SCHEMA SMS_BE --> SCHEMA CRON --> ASSIGN SHEET --> GSHEETS SMS_BE --> TWILIO DRIVERS --> CLERK style DISPATCH fill:#e0f2fe,stroke:#0284c7,color:#0c4a6e style CONVEX fill:#ecfdf5,stroke:#059669,color:#064e3b style EXTERNAL fill:#fef3c7,stroke:#d97706,color:#78350f

Recent Activity (Feb 25 – Mar 10)

FEATURES

Mar 10
Driver Day Off Toggle
New driverDayOffs table enables per-date temporary unavailability. Greyed-out card in Daily view, drag-drop blocked, Smart Copy and cron skip day-off drivers.
Mar 9
WebReel Screen Recorder
Keyboard-driven screen recording overlay (OPT+CMD+R) with no React tree dependency. Self-injects via ReactDOM.createRoot to escape gesture handler stacking context.
Mar 1
Mixed Weeks Report
New report tab identifying children with irregular weekly driver pairings. Queries routes via getWeekAssignments, exports to Google Sheets with per-slot driver columns.
Mar 1
Dual-Column Sheet Sync
Auto-detects legacy vs new sheet layout in sheetSync.ts. Wider fetch range (A1:X200) for new driver columns O-X. Skip patterns for schedule notes.
Feb 26
Full-Week Sunday Cron
Replaced daily +7-day cron with weekly Sunday 10 PM PST cron that stages Mon–Fri in one shot. Dispatchers arrive Monday with a fully-staged week.
Feb 25
Weekly Route Population Script
populate-week-routes.cjs: comprehensive fuzzy-matching script for importing sheet assignments. Removed carpool max limit of 3 — drivers can serve unlimited children per slot.

FIXES & MAINTENANCE

Mar 10
Weekly Search Bar CSS
Added border to search container, removed browser-default blue focus outline on input.
Feb 25
Cron Holiday Logic
Skip source dates with 3+ school closures to prevent thin copies propagating. Fixed auditLogs schema strategy field.
Mar 10
TestIDs + Clerk Linking + Gitignore
Added testID attributes across UI for automation. New linkClerkIdById mutation. Cleaned up gitignore for .claude/, .expo/, videos/.
Mar 1
Executive Summary
Stakeholder update document for March 1, 2026.

Decision Log

Day Off as a separate table vs. a field on drivers
Chose driverDayOffs table with by_driver_date + by_date indexes. Avoids polluting the drivers table with date-keyed arrays. Each record is one driver + one date. Toggle pattern: insert or delete. No "reason" field required initially but schema supports it as optional.
Day Off covers both AM and PM (whole day)
Simpler UX: one toggle per day. If a driver is off for a doctor appointment, they're off the whole day. Per-period day-offs would add complexity with minimal value for the current use case.
Removed carpool limit of 3 children per driver slot
Production data showed drivers regularly serve 4-6 children. The hard cap was from early prototyping. Child uniqueness constraint (one child per date+period) remains the real safety net.
Weekly cron replaces daily cron
Daily +7-day cron caused cascading thin copies when source dates were holidays. Weekly Sunday cron stages entire Mon–Fri at once. Falls back gracefully: per-day errors are logged without halting the loop. Layer 2 sheet-delta handles Cathey's Sunday updates on top.
Day-off drivers remain visible but greyed out (not hidden)
Dispatchers need to see who is off — hiding them would cause confusion ("where did Renato go?"). Visual: 45% opacity, grey background, orange "Day Off" badge, red calendar icon. Drag-drop and route creation are blocked at both UI and backend levels.

State of Things

12
Features Shipped
3
Open Branches
1
Blocked
0
Uncommitted
Working
  • Core dispatch drag-drop pairing
  • Calendar + Smart Copy
  • Full-week Sunday cron
  • SMS conversations (1:1 + groups)
  • Driver Day Off toggle
  • Mixed Weeks report
  • Weekly route population script
  • A2P 10DLC campaign approved
In Progress
  • 3 stale branches (SMS group dedup, viewport fix, theme wrap)
  • 11 files with TODO/FIXME comments
  • WebReel screen recorder (skill refinement)
Blocked
  • Carrier SMS delivery approval — pending external approval for production SMS to real phone numbers

Mental Model Essentials

Invariant

One child per date+period slot (by_child_date_period index). This is the core scheduling constraint — everything else flows from it.

Invariant

Driver availability is 3-tier: On Hold (persistent roster removal) → Weekly Availability (recurring day/period toggles) → Day Off (one-off per-date). Each checked in order during getUnassignedDrivers.

Coupling

assignments.ts is the gravity well: 1,631 lines, touched by Daily view, Weekly view, cron, Smart Copy, and reports. Changes here ripple widely.

Coupling

Schema changes require npm run sync:types to copy convex/_generated to dispatch-app/convex/_generated. Forgetting this causes TypeScript errors in the frontend.

Gotcha

Never run npx convex dev from subdirectories. Must be project root. See CONVEX_DEV_WORKFLOW.md.

Gotcha

Phone numbers must be E.164 format (+1XXXXXXXXXX). The normalizePhone() helper in drivers.ts handles this for new entries.

Pattern

Webhooks use internalMutation, not public mutations. Clerk webhooks, Twilio inbound/status — all internal.

Pattern

The codebase uses .web.tsx suffixes for platform-specific web implementations (e.g., DraggableCard.web.tsx vs DraggableCard.native.tsx).

Cognitive Debt Hotspots

High

AssignmentScreen.tsx — 2,602 lines. The largest component in the app. Contains drag-drop logic, carpool state, rate modals, inline editing, sort/filter, add-child modal, and now Day Off toggle. Consider extracting drag-drop handler and carpool state into a custom hook.

High

assignments.ts — 1,631 lines, 20 exports. Handles route CRUD, queries, Smart Copy, scheduling intelligence, and reminders. The copyFromDate mutation alone is ~80 lines with 3 different skip conditions (duplicates, day-offs, holidays). Splitting into assignments-queries.ts and assignments-mutations.ts would help.

Medium

3 unmerged stale branchesall-container-theme-wrap, feat/sms-group-deduplication, fix/sms-groups-viewport-layout. These may contain useful work or may be abandoned. Review and prune.

Medium

No test suite. Zero automated tests across the entire codebase. The safety net is manual browser verification and Convex's type system. As the codebase grows (40 tables, ~280 functions), critical paths like copyFromDate and getUnassignedDrivers should have unit coverage.

Low

11 files with TODO/FIXME markers in convex/ — including schema.ts, publish.ts, googleSheets.ts, drivers.ts. Audit and resolve or document as intentional.

Low

Chat window z-index unification — documented in docs/TODO-chat-window-zindex-unification.md. Multiple chat windows spawning in the same render cycle all get maxZ + 1. Low priority but causes initial lag with simultaneous inbound SMS.

Next Steps

  • Production SMS testing — carrier delivery approval is the last gate. Once approved, test with real phone numbers and roll out to drivers.
  • Week of Mar 9-13 operations — routes are staged via Sunday cron. Monitor Day Off usage and iterate on dispatcher feedback.
  • Sheet sync automationpopulate-week-routes.cjs works but requires manual SHEET_ROWS updates. Wire to Google Sheets MCP for automatic weekly pulls.
  • Branch cleanup — review 3 stale branches, merge or close.
  • AssignmentScreen decomposition — extract drag-drop hook and carpool state management to reduce the 2,602-line monolith.
  • WebReel demo refinement — skill is installed, recorder works. Create polished demo videos for stakeholder presentations.
Generated Mar 10, 2026 · Go Happy Cab · Ma ka hana ka ʻike