# OnWay > OnWay is a local delivery platform in any city. Customers order from fictional local stores and restaurants, independent drivers pick up and deliver, and dispatchers monitor the full fleet from a live map. AI fills carts by voice, checks delivery photos, and answers support questions. All financial flows use Stripe sandbox — no real money moves. This page explains how it works, in plain text, for people and AI assistants. Site: https://onway.lycore.com Animated walkthroughs: https://onway.lycore.com/how-to Six-minute tour of the whole app: https://onway.lycore.com/how-to/tour.html This file: https://onway.lycore.com/llms.txt --- ## 1. Core concepts - **City**: one configurable service area per deployment. All stores, addresses and routes are within that area; every monetary amount is in USD. - **Role**: three roles in one codebase — Customer, Driver and Dispatcher. Role-based routing sends each to a different start screen. A user can be given any role; showcase visitors switch roles from the top bar without signing in. - **Order lifecycle**: a server-enforced state machine. States: `placed → accepted → assigned → driverAtPickup → pickedUp → arriving → delivered`. Terminal states: `cancelledByCustomer`, `cancelledByStore`, `noDriverFound`, `deliveryFailed`. Only the `transition_order_state` RPC can advance the state; the client never writes the `status` column directly. Every transition is timestamped and appended to `order_events`. - **Money**: all amounts are stored in integer cents throughout the stack. Float arithmetic is never used for totals; rounding happens once at input. - **Sandbox payments**: Stripe sandbox only — card 4242 4242 4242 4242 always succeeds. A notice at checkout reads "Sandbox payments: no real money is charged." No real charges are ever made. - **Promo code**: one seeded code, `LYDASHFREE`, gives free delivery on the first three orders. - **Fictional data**: all store names, menus, driver names and order data are invented. No real restaurant chains, no real product photos from brands. Service area uses real city geography with fictional businesses at plausible addresses; no real private home addresses. - **Showcase reset**: all order and driver data resets nightly. Personal data in visitor accounts is fictional. - **No secret keys**: only publishable/public keys appear in the app bundle (`SUPABASE_ANON_KEY`, `STRIPE_PUBLISHABLE_KEY`). All Claude API calls are routed through a Supabase Edge Function. ## 2. Actors | Actor | What they do | Lands on | |---|---|---| | Customer | Browses stores, builds a cart, checks out, tracks the driver live, chats with the driver, rates the delivery, views receipts. Can order by voice or text via the AI concierge. | Explore (home) | | Driver | Goes online, receives 30-second job offers, accepts or declines, navigates to pickup and drop-off, captures proof of delivery with optional signature, logs earnings. | Job offers | | Dispatcher | Watches every online driver and active order on a live map, sees late and unassigned flags, reads an AI fleet summary, reassigns drivers, approves flagged proof photos, reviews audit log. | Dispatcher console | | Showcase visitor (Customer) | Pre-made account (`visitor@onway.app`), saved addresses and sandbox card on file, 5 past orders. No sign-up. | Explore (home) | | Showcase visitor (Driver) | Pre-made account (`driver@onway.app`), 2019 Toyota Corolla TX-8841, Approved status. No sign-up. | Job offers | | Showcase visitor (Dispatch) | Pre-made account (`dispatch@onway.app`), sees all drivers and orders. No sign-up. | Dispatcher console | The three showcase visitor accounts are available from the welcome screen. Roles can be switched from the top bar at any time. Data resets nightly. ## 3. Customer app **Explore / Home** (`/explore`): nearby stores sorted by distance or fastest ETA. Categories: Food & Dining, Quick Grocery, Pharmacy, Artisanal Florals, Late Night. A search bar and AI voice mic appear at the top. A promo banner shows active codes. **Store page** (`/store/{id}`): hero image, opening hours, delivery fee, minimum order, star rating. Items grouped by category (Popular, Mains, Sides, Drinks). Each item has name, description, price, size options (Regular / Large, with price delta), and optional add-ons. Tapping an item opens a bottom sheet with quantity, size and add-on selectors. **Cart and checkout**: item list with quantity controls, subtotal, delivery fee ($0–$2.99, distance-based), service fee (10%), tax (8.25% default local rate), driver tip selector (15 / 18 / 20 / 25% or custom). Leave-at-door vs hand-to-customer toggle. Delivery instructions text field. Saved addresses pre-fill the drop-off. Stripe sandbox card on file, or a new card can be entered. Placing an order writes to `public.orders` and creates an initial `order_events` row via the `transition_order_state` RPC. **Live tracking** (`/tracking`): real-time map (flutter_map + OpenStreetMap tiles) showing the store pin, the drop-off pin and the driver's moving marker. Order lifecycle steps appear as a progress strip. ETA countdown updates as the driver moves. "Driver nearby" triggers at 0.3 miles. A masked chat button opens the in-app relay. Screen stays active while the order is live. **Masked chat**: customer and driver exchange messages through a relay. Neither sees the other's real phone number. Messages are labelled "You" and the driver's first name. **After delivery** (`/complete`): the customer sees the proof-of-delivery photo the driver submitted, a star-rating prompt (1–5 stars for driver and store, optional comment), and the full itemised receipt (subtotal, fees, tip, tax, total). Receipt can be shared as text. **AI ordering** (AI Order tab, `/ai`): a chat and voice interface. The customer describes what they want in plain language or by voice; the AI proposes a cart using a `build_cart` tool call. A cart preview card is embedded in the chat thread. Customer confirms; cart is populated. Past order history is available to the AI so "my usual" resolves correctly. AI never places an order or charges a card. **AI support** (in the live tracking screen): the customer can ask "Where's my order?", report a missing item (creates a support ticket; no automatic refund), cancel before pickup, update delivery instructions, or escalate to a human dispatcher. Answers only from live Supabase data; never from memory. **Order history** (Profile tab): past orders with date, store, item summary and total. One-tap Reorder rebuilds the cart. **Profile** (`/profile`): email or phone sign-in, saved addresses (public non-residential local points), saved sandbox card, 5 seeded past orders. ## 4. Driver app **Online / offline toggle**: location is tracked only while online. Drivers can only receive job offers while online. **Job offer screen**: incoming orders appear as a card showing pickup store, drop-off area, distance, base pay and estimated tip. Driver has 30 seconds to accept or decline. If declined or timed out, the offer moves to the next nearest available driver (selected by the `find_nearest_available_driver` PostGIS RPC). The 30-second countdown is shown live. **Active job flow**: after accepting, a step-by-step screen guides the driver: 1. Drive to store 2. Enter store / confirm items (`driverAtPickup`) 3. Pick up (`pickedUp`) 4. Drive to customer 5. Submit proof of delivery (`delivered`) Each step has a large action button that calls `transition_order_state`. An in-app route map is shown at every step; driver can tap "Open in Google Maps / Apple Maps" for native navigation. **Proof of delivery**: driver takes or selects a photo at the drop-off, then optionally captures a customer signature on screen (`signature` package). The photo is sent to Claude Haiku (via the Edge Function) for an AI vision check. Result: `delivered_clear`, `delivered_unclear`, or `failed`. If unclear or failed, the driver is prompted to retake (one retry allowed; max 2 checks per delivery). The dispatcher is notified and the photo is flagged. Delivery proceeds regardless — the flag is for human review only. **Delivery failed flow**: if drop-off cannot be completed, the driver selects a reason (customer unreachable, address issue, unsafe), attaches a photo, and initiates a return. This sets the `deliveryFailed` terminal state. **Earnings** (`/earnings`): today's total, number of trips, tips kept, and a full history of completed trips (store name, drop-off area, base pay, tip, total, miles, completion time). **Driver profile / vehicle**: name, vehicle type (Hybrid Sedan, EV, Cargo Scooter, Pickup Truck), licence plate, star rating, total deliveries. A placeholder "Approved ✓" badge appears in lieu of a background-check screen (out of scope for v1). ## 5. Dispatcher console **Metrics bar**: orders today, average delivery time, on-time %, and live active driver count — shown as headline tiles above the order table. **Live map**: all online drivers appear as colour-coded pins (green = online/idle, yellow = on job, grey = offline). Active orders appear as route lines from store to drop-off. The map updates every 3 seconds (simulated in the showcase). 12 seeded drivers (DRV-01 through DRV-12) are always moving on the map during the showcase. The configured city bounds are the default viewport. **Order table**: lists all active orders with order ID, store name, customer name, assigned driver, status, ETA label and flag indicators. Filters: All, Late, Unassigned, Flagged. Flags: - **Late**: ETA exceeded by more than 10 minutes. - **Unassigned**: no driver accepted within 5 minutes of placement. - **Flagged photo**: AI proof check returned `delivered_unclear` or `failed`. **Manual actions (logged to `public.audit_log`)**: reassign a driver to an order, cancel an order with a reason (releases Stripe authorisation), approve a flagged proof photo, contact the customer or driver via the masked relay. All actions are recorded with actor, action, order ID and timestamp. **Audit log modal**: a live viewer showing every state transition and dispatcher action, append-only, with timestamp and actor. **AI fleet summary** ("What needs my attention?"): sends the current order table and driver positions to Claude Sonnet and returns a plain-language paragraph covering late orders, unassigned orders, flagged photos and open support tickets. Read-only; shown in a dismissable panel. ## 6. AI features (Claude) All AI calls are routed through the Supabase Edge Function (`lydash-ai-and-stripe/index.ts`). No secret API keys appear in the app bundle. - **Model for ordering and support**: Claude Sonnet (current version per Anthropic docs). - **Model for photo checks**: Claude Haiku (vision, faster and cheaper for classification). | Feature | Model | Daily cap | |---|---|---| | Chat / voice ordering messages | Sonnet | 40 per user | | AI support agent messages | Sonnet | 40 per user | | Proof-of-delivery photo checks | Haiku | 2 per delivery | | Dispatcher fleet summaries | Sonnet | 30 per day | **Voice ordering**: speech transcribed client-side (Web Speech API on web, `speech_to_text` on mobile), sent as text to the AI chat endpoint. **Cart fill tool (`build_cart`)**: AI calls the tool with a structured list of items and quantities. Client renders a cart preview card in the chat thread. Customer taps Confirm to apply it. **Support agent**: answers "Where's my order?", handles missing-item reports (creates a `support_tickets` row; no automatic refunds), cancels before pickup, updates delivery instructions, escalates to human dispatcher. All actions require user confirmation. Never modifies data without a confirmed action. **Photo check**: driver's photo uploaded to Supabase Storage, URL passed to Claude Haiku vision with a structured prompt. Returns `delivered_clear`, `delivered_unclear` or `failed`. Verdict stored in `public.proofs.ai_check_json`. One retry allowed; max 2 checks per delivery. **Fleet summary**: serialises current `orders` and `driver_status` rows to JSON, passes to Claude Sonnet. Returns a plain-language summary. Read-only. All AI requests logged to `public.ai_usage` with user ID, feature, model and tokens. Daily cap enforced by the `check_and_log_ai_usage` RPC (defined in the schema; client-side UI counter also shown). ## 7. Backend and data **Database**: Supabase Postgres with PostGIS extension. **Project URL**: `https://jekriznwiccjnizsdack.supabase.co` **Key type**: public/anon (`sb_publishable_EkFXgt3f5ubr4Exdd6afaw_t9wlexGx`). Row-level security enforces per-role access. Key tables: | Table | Notes | |---|---| | `public.orders` | One row per order. `totals_json` stores all amounts in integer cents. `dropoff_location` is `GEOGRAPHY(Point, 4326)` (PostGIS, longitude-first). `payment_intent_id` format: `pi_sandbox_{orderId}_4242`. | | `public.order_events` | Append-only log of state transitions. Fields: `id`, `order_id`, `from_status`, `to_status`, `actor` (`customer`/`driver`/`dispatcher`/`system`), `at`. | | `public.driver_status` | Live driver location. `location` is `GEOGRAPHY(Point, 4326)` with GIST index. `heading`, `online`, `active_order_id`. | | `public.location_pings` | Per-ping GPS history. 7-day retention via `cleanup_old_location_pings` scheduled function. | | `public.job_offers` | One row per offer. `offered_at`, `expires_at` (30 seconds after offer), `response` (accepted/declined/expired). | | `public.proofs` | `photo_path` (Supabase Storage), `signature_path`, `ai_check_json` (verdict + reason from Claude). | | `public.chats` / `public.chat_messages` | Masked relay. Sender role and order ID stored; phone numbers never stored. | | `public.support_tickets` | Created by AI support agent on escalation or missing-item report. | | `public.audit_log` | Dispatcher actions. Append-only; no DELETE or UPDATE grants on this table. | | `public.ai_usage` | AI request log with user, feature, model, tokens. | | `public.profiles` | User profile, role, display name, masked phone. | | `public.addresses` | Saved addresses. `location` is `GEOGRAPHY(Point, 4326)`. | | `public.stores` | Store catalogue. `location` is `GEOGRAPHY(Point, 4326)`. | | `public.items` | Menu items. `price_cents` integer. `options_json` for size and add-on variants. | | `public.ratings` | 1–5 star ratings for driver and store, optional comment. | **RPCs**: - `transition_order_state(order_id, next_status, actor)` — idempotent; enforces the state machine; rejects invalid transitions. - `find_nearest_available_driver(order_location)` — PostGIS nearest-driver query; returns the closest online driver with no active order. - `check_and_log_ai_usage(user_id, feature, model, tokens)` — checks the daily cap, logs the call, returns whether the call is allowed. **Storage**: proof-of-delivery photos stored in a private Supabase Storage bucket (`proof-photos`). Signed URLs expire after 1 hour. **Edge Function** (`lydash-ai-and-stripe/index.ts`): handles Stripe sandbox authorise / capture / release and all Anthropic Claude API calls. Never exposes secret keys to the client. **Row-level security**: customers see only their own orders. Drivers see only orders offered to or assigned to them. Dispatchers see all orders and all driver status rows. ## 8. Web simulation The Flutter web build cannot use background GPS or receive push notifications; the field side is simulated: - **12 seeded drivers** (DRV-01 through DRV-12) with varied vehicles move continuously on the dispatcher map every 3 seconds. - **Auto-accept**: when a visitor places an order on web, a simulated driver accepts within 10–20 seconds and the order advances through the full lifecycle automatically, including a pre-made proof-of-delivery photo. - **"Try as driver" on web**: the visitor advances job steps manually via buttons; the route follows automatically. The screen notes that real GPS is used only on mobile builds. - **Simulation uses the same state machine RPC** as real drivers — no separate fake code path in the customer app. ## 9. Implementation status Fully implemented in the Flutter web showcase: - All role UIs: Customer (Explore, Store, Cart, Checkout, Tracking, AI Order, AI Support, Profile), Driver (Job Offers, Active Job, Proof of Delivery, Delivery Failed, Earnings), Dispatcher (Console map, Order table, Audit log, AI Summary) - Order state machine, `order_events`, `audit_log`, live Supabase REST / RPC integration - Masked chat, signature capture, delivery failed flow, driver earnings history - Dispatcher metrics bar, order filters (All / Late / Unassigned / Flagged), manual actions - AI ordering (chat + voice simulation), AI support, AI photo check, dispatcher AI summary - 12 simulated drivers moving on map, auto-accept and full order lifecycle on web Defined in the Supabase schema and Edge Function but not yet wired to real external services (web showcase uses simulation or stubs): - **Stripe PaymentSheet / real authorise-capture-release**: checkout UI is present; no real Stripe call yet. - **FCM push notifications**: in-app live banners only; no Firebase Cloud Messaging. - **Native camera plugin** (`image_picker` / `camera`): web uses file picker or pre-made proof image. - **Background GPS** (`geolocator` + background location package): web uses simulated route; native mobile plugin pending mobile build phase. - **Server-side AI cap enforcement**: `check_and_log_ai_usage` RPC is defined; client-side UI counter enforces caps in the showcase. - **Mapbox Directions routing**: ETAs and route lines are simulated; no real routing API call. - **Nightly data reset**: schema supports it; Supabase cron job not yet configured. ## 10. Seed data - **~25 fictional local businesses** across 5 categories with invented names, menus and options: - Food & Dining: Lone Star Smokehouse & Greens, Verde Vista Taqueria, Velvet Roast Cafe & Bakery, Neon Owl Wood-Fired Pies - Quick Grocery: Barton Springs Organic Market - Pharmacy: Congress Avenue Apothecary - Artisanal Florals: Lady Bird Botanicals - Late Night: Neon Owl Wood-Fired Pies (dual category) - **12 simulated drivers** (DRV-01 through DRV-12) with varied vehicles (sedan, EV, cargo scooter, pickup truck). - **Visitor customer account** (`visitor@onway.app`) with 5 past orders (LD-8120 through LD-7492, including "my usual" from last Friday) and 3 public non-residential local drop addresses: - South Congress Plaza — 1500 S Congress Ave - Zilker Park Commons — 2100 Barton Springs Rd - Seaholm District Courtyard — 222 West Ave - **Sandbox card on file**: Visa •• 4242 (Stripe sandbox). - **One promo code**: `LYDASHFREE` — free delivery, shown on the home banner and active at checkout. ## 11. Out of scope (v1) - App store publishing. - Real payouts to drivers; driver background checks and document verification. - Multi-store carts, scheduled orders, subscriptions. - Store / merchant app (stores auto-accept after a short delay in v1). - Alcohol or age-restricted items. - Address autocomplete with a live geocoding API (addresses are pre-seeded). ## 12. Security - Only publishable keys in the app bundle; all secret keys live in Edge Function environment variables. - All monetary values are integers (cents); no float arithmetic for totals. - State machine RPC is the single path for order state changes; direct `status` column writes are blocked by RLS. - Masked chat: phone numbers never stored; only sender role and order ID. - Proof photos in a private Supabase Storage bucket; signed URLs expire after 1 hour. - `audit_log` table has no DELETE or UPDATE grants; append-only. - Location pings retained for 7 days, then deleted by `cleanup_old_location_pings`. - Background GPS tracking stops immediately when the driver goes offline or closes the app. - State transitions are idempotent (double-taps or retries do not double-advance an order). ## 13. Glossary - **PostGIS**: PostgreSQL spatial extension; stores delivery coordinates as `GEOGRAPHY(Point, 4326)` columns. - **SRID 4326**: WGS 84 coordinate reference system (standard GPS latitude/longitude, longitude first in PostGIS). - **Stripe sandbox**: test payment environment; card 4242 4242 4242 4242 always succeeds, no real money moves. - **Masked chat**: relay between customer and driver; neither sees the other's phone number; only role and order ID are stored. - **Proof of delivery (POD)**: photo captured by driver at drop-off, AI-checked for clarity by Claude Haiku. - **State machine**: server-enforced order lifecycle via `transition_order_state` RPC; invalid transitions are rejected. - **ETA**: estimated time of arrival; shown on the customer tracking screen and in the dispatcher order table. - **Late flag**: set when actual delivery time exceeds the ETA by more than 10 minutes. - **Unassigned flag**: set when no driver accepts an order within 5 minutes of placement. - **Flagged photo**: POD photo where Claude Haiku returned `delivered_unclear` or `failed`. - **Distance filter**: minimum movement (≈15 m) required before a location ping is sent, to reduce battery drain when stationary. - **Background location**: GPS tracking active only while the driver is online with an active job; stops immediately on going offline (native mobile builds only; simulated on web). - **`build_cart` tool**: Claude tool call that returns a structured cart (items + quantities); client renders a preview card for customer confirmation. - **`transition_order_state` RPC**: idempotent Supabase RPC that enforces the order state machine and appends to `order_events`. - **`find_nearest_available_driver` RPC**: PostGIS spatial query that returns the closest online driver with no active order. ## 14. Main URLs `/` (welcome / role selector) · `/explore` (customer home) · `/store/{id}` (store detail) · `/tracking` (live order map) · `/ai` (AI ordering) · `/complete` (delivery receipt) · `/profile` (customer profile) · `/driver` (driver home / job offers) · `/earnings` (driver earnings) · `/dispatch` (dispatcher console) · `/how-to` (public walkthroughs) · `/how-to/tour.html` (six-minute tour) · `/llms.txt` (this page)