Three new modals wired into the planner with real interactions:
CustomiseStopModal (4 tabs)
- Tab rail (Charger / Overnight / Duration / Things to do / Detour)
auto-hides tabs that don't apply (e.g. Charger only for chargers,
Overnight only for hotels, Things to do only when nearby[] exists).
- Charger tab: radio-style picker over stop.chargerOptions with
kW/stalls/€/network/detour badge per option.
- Overnight tab: lists the current hotel + stop.alternatives as
swappable options with Choose buttons.
- Duration tab: 48px figure + range slider (10–120 min, snaps to
presets) with arrive/leave battery % shown live; preset grid
(Quick top-up / Coffee / Sit-down lunch / Explore / Full charge /
Skip).
- Things to do tab: checkbox grid over stop.nearby[] for picking what
you want to do at the stop.
- Apply Changes commits chargeMinutes + durationMin back into the
itinerary via a new updateStop() handler.
AddDetourOverlay
- Spotlight-style search bar (autofocus, esc to close) with a
curated POPULAR_DETOURS list (York Minster, Lake District,
Hadrian's Wall, Beaune town centre, etc.). Insert button adds the
detour as a new stop in the current day via a new insertDetour()
handler. The map polyline / leg metrics recompute automatically.
GpxExportModal
- Real downloadable export — GPX (XML), KML (Google Earth), CSV.
- Live preview pane shows the generated file, with line count + KB
in the footer. Copy puts the file content on the clipboard;
Download .gpx writes it as a Blob with the right MIME type and
triggers a real browser download. Filename derived from the trip's
first/last stop.
- GPX include-toggles for stop notes and nearby places.
- Empty-trip state shows a friendly message instead of an empty pane.
Plumbing
- Modal state lifted into the planner as { kind, stopId? } | null.
- Top bar Export button, the rail's Add stop button, and the bottom
Add detour link all open the relevant modal.
- The Customise stop button inside the expanded stop card now opens
the Customise modal for that stop.
- ModalShell handles backdrop, escape-to-close, scroll-clamp, and
optional header/footer/subtitle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- NightBlock component: dashed-blue panel that appears under the last
stop of any day (when it's a hotel or supercharger) and before the
next day starts, showing "Sleep in car at services" or the hotel
name with a "Hotel options" button that opens the stop's expansion
to its overnight swap section.
- Drag-to-reorder: every stop card is draggable; dropping on another
card moves the dragged stop into that position (and into that
day if you drag across day boundaries). Order numbers are
renormalised after each drop; the legs + map polylines + day totals
recompute automatically.
- Origin/Destination editable replan: editing the origin or
destination input and pressing Enter or blurring sends a "Replan
the trip from {origin} to {destination}" chat message. Auto-
populating from the current itinerary does not trigger a replan;
only real user edits do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the three big "options" UX wins from Direction B:
1. Route variants (Fastest / Scenic / Cheapest)
- Grok prompt now returns a top-level variants[] summary with
drive/charge/cost/distance/pros for each variant, plus a
selectedVariant indicating which one the stops reflect.
- VariantStrip renders under the top bar with selected-state
styling, tone-coloured highlight (red/green/blue) on the most
relevant stat, and 3-5 pros pills.
- Clicking a variant fires /api/chat with selectedVariant=<id> so
Grok re-plans with that variant's bias. A "switching" state
disables the strip while the request is in flight.
- The chat route accepts selectedVariant ('fast'|'scenic'|'cheap')
and the GrokHeadlessClient threads it through both the local CLI
and xAI API paths.
2. While here (food / do / see / shop / rest)
- Every Supercharger, destination-charger and hotel stop now
returns a nearby[] array with category/icon/name/detail.
- Expanded stop card has tabs (All / Food / Do / See) — tabs
auto-hide when no items in that category. Two-column grid of
named places with walk-time + rating, e.g. "M&S Foodhall · 1 min
walk · 4.3★ · sandwiches".
3. Charger swap block
- Every charging stop now returns chargerOptions[] with the
current pick + 1-3 alternatives at the same location, each with
network (Tesla/Ionity/Allego/Fastned/BP Pulse), stalls, kW,
pricePerKwh, detourMin and an optional badge (Faster/Cheaper/
More stalls/Newer).
- ChargerSwapBlock shows the current charger as a red-tinted
header row that expands to reveal alternatives with stats and a
Use button per row.
Renamed the existing AlternativeStop UI label from "alternative(s)"
to "location alternative(s)" so it's clear when the user is swapping
the stop *location* vs swapping the *charger at the same location*.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the layout-shell phase of the Direction B redesign from the
Anthropic Design handoff bundle.
Visual layer:
- Geist font + new --gd-* CSS token palette (panel/border/text scales,
green/amber/blue/purple accents alongside the red).
- Top bar with brand chip, origin/destination inputs, chat composer
with chip-based refinements, vehicle selector chip, Export/Share.
- Body split: map-left flex-1, stops rail 540px right (replaces the
prior chat-sidebar + bottom-timeline layout).
- Stops rail: trip summary header (stops · days · km · drive · charge),
sticky day headers with date label + per-day km/drive/charge totals,
icon-led stop cards with colored type-icon tile (charge/sleep/hotel/
attraction/cafe/viewpoint colour-coded), combo pill, description,
charge/arrive meta inline.
- Leg row between cards (km · drive time), still computed from real
OSRM routes.
- Click stop to expand: 4-stat grid (Charge/Arrive/Leave/Cost),
amenity chip cloud, alternative-swap list (preserving the existing
delta swap mechanic), Customise/Remove actions.
- Map ↔ rail bridge: clicking a card flies the map to that pin,
hovering either highlights the other. Pins are custom Leaflet
divIcons coloured per stop type with active/hover scaling.
- Empty state + quick-prompt buttons in the rail.
Not in Phase 1 (deferred):
- Variant strip (Fastest/Scenic/Cheapest) — needs backend prompt work.
- While-here food/do/see tabs — needs backend nearby[] data.
- Customise / Add-detour / GPX modals.
- Vehicle selector trim panel (still a native select for now).
- Drag-to-reorder, in-trip/compare/shared screens.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Timeline now shows the driving distance and duration between every
consecutive pair of stops, taken from real OSRM road routes (not Grok
estimates). Top stat bar and per-day headers also use the live totals
so they update immediately as the itinerary changes.
Grok now returns 1-3 alternative picks for each Supercharger and
hotel stop, each with deltaKm/deltaMin vs the chosen pick and a short
reason explaining the trade-off. The Swap (n) button on each card
expands an inline list of alternatives; clicking one swaps the stop
in place. The previously-chosen stop is kept in the alternatives
list with inverted deltas so the user can swap back. The map +
polylines + stat bar all recompute automatically.
Other tweaks:
- haversine fallback when OSRM is unreachable so legs still show
approximate metrics in offline / degraded mode.
- Leg geometry storage moves from raw polyline[] to typed Leg[] with
per-leg distance/duration/fromId/toId.
- Stop schema gains alternatives[]; client normalization filters out
alternatives missing lat/lng.
- Day cards widened (340 → 360px); timeline pane grew 280 → 340px
with vertical scroll so swap panels don't clip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add tsconfig.json (server) + client/tsconfig.{json,app.json,node.json}
so typecheck and tsc -b actually work.
- Fix npm test to run Playwright (was running vitest on Playwright specs);
typecheck now covers both server and client.
- Mount routes before app.listen, add error handler, mount optional
@tonycodes/auth-express middleware when AUTH_SECRET is set.
- Add /api/trips (GET/POST/PATCH/DELETE) backed by an in-memory store
that gracefully degrades when DATABASE_URL is unset.
- Add prisma/seed.ts skeleton and server/types/express.d.ts for req.auth.
- Rewrite Grok prompt for combo-aware planning: charge+eat,
stay+destination-charging, eat+viewpoint, etc., with amenities,
cuisine, priceLevel, duration, day titles and trip highlights.
- Extend Stop schema + normalization to preserve all enrichment fields.
- New StopCard component renders combo pill, description, meta row
(charge / stop / battery / cuisine / £-level) and amenity icons;
map popups show the same enriched detail; timeline gains day titles
and a HIGHLIGHTS sidebar.
- Fix server TS errors (vehicle accepted as string | {name,rangeKm},
JSON parse results typed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Proactive Grok integration (xAI API + local CLI fallback)
- Real road routing via OSRM (no more bird's-eye lines)
- Heavy structured logging for fast iteration
- Strong sanitization + geocoding + ErrorBoundary (no black screens)
- Playwright E2E tests (API diagnostic + full UI flow)
- scripts/dev.sh for one-command startup
- Clean .env.example + documentation
This is a stable checkpoint before further prompt/UI refinement.