Skip to content

Mobile-first /plan redesign: responsive shell + TripBuilder decomposition#31

Merged
johnymontana merged 2 commits into
mainfrom
mobile-redesign
Jul 7, 2026
Merged

Mobile-first /plan redesign: responsive shell + TripBuilder decomposition#31
johnymontana merged 2 commits into
mainfrom
mobile-redesign

Conversation

@johnymontana

@johnymontana johnymontana commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Rebuilds the /plan trip planner into a mobile-first responsive shell, decomposes the 900-line TripBuilder monolith into a provider + pure components, and lands the full polish backlog plus an experimental draggable bottom sheet. This is the complete docs/PLAN-UX-REDESIGN.md (Phases 0–3); the design is recorded as ADR-076 in docs/DECISIONS.md.

Why

On a 390×844 phone the old 55/45 split gave the builder a ~430px peephole (the 380px map alone nearly filled it) and starved the chat; components/plan/ had zero responsive styling. Ranger edits also landed invisibly in the other pane, and the trip window / delete / day-plan persistence had no UI at all. Now map, itinerary, and ranger are each usable full-screen, cross-pane updates are visible, and the trip lifecycle is complete.

Phase 0–1 — the shell + decomposition

  • PlanShell.tsx — one client CSS grid: "itinerary map chat" on md+, a single full-viewport pane behind a bottom PlanTabBar on base. Everything mounts once; visibility is pure CSS (no useBreakpointValue markup branch), so the Eve chat session and the maplibre canvas never churn on a tab switch (ADR-017 hydration-safe). Compressible minmax rails so the map never collapses on 768–780px portrait tablets. ?pane= URL contract (from=graph → Ranger?pane=?trip= → Itinerary) with a surgical seed/from replaceState.
  • TripBuilderProvider / useTripBuilder.tsx owns all trip state + ops (postOp with uniform 401/429 handling, refreshTrip that preserves an unsaved day plan and defers a mid-drag refresh). Pure pieces: TripSwitcher, TripHeader, StopList, TripActions, TripInsights. TripBuilder.tsx is the thin composition used at every breakpoint. ChatPanel stays provider-free (window events only) so the /learn lesson player is unaffected.
  • Cross-pane glue (F8): message-id-deduped trailgraph:trips-changed (stream + settled fire) and a new trailgraph:ranger-activity unread signal.
  • Map lifecycle: init-gated construction (no 0×0 birth), reveal-time reframe, cooperativeGestures prop, getSource-guarded installLayers fix for the basemap-fallback wipe.
  • Server: split the trip-mutation rate limit into tripmut (30/60s, writes) and tripread (60/60s, reads).

Phase 3 — polish backlog (default-on)

  • Trip dates editor (setDates op, YYYY-MM-DD nullable, endDate ≥ startDate guarded).
  • Trip delete — confirm dialog → the now rate-limited DELETE route.
  • Persisted day plans (applyDays writes stop.day; a reorder clears it) — plans survive reload.
  • Keyboard reorder (Up/Down buttons) — motion's Reorder is pointer-only, a real a11y gap.
  • Day-grouping section headers with per-day load chips.
  • Empty states — a no-trips hero (with a ranger handoff) and an empty-trip checklist.
  • Native share (navigator.share → clipboard fallback) and a tab alert badge.
  • Map add-flow — a React bottom card + an invisible fat hit layer + a pulse on add.
  • /plan chat reload persistence:PlanTranscript (migration 029) + /api/plan/transcript, replaying initialEvents with a dedup-ref seed so replayed turns don't re-fire events.

Phase 2 — bottom sheet (behind NEXT_PUBLIC_PLAN_SHEET, off by default)

PlanSheet.tsx — a full-bleed map + a draggable itinerary sheet (peek/half/full snaps, velocity-projected settle, tap-the-handle to cycle for prefers-reduced-motion, native body scroll only at full); the Ranger becomes a FAB overlay. The flag is build-time → identical on server and client, so the layout branch is hydration-safe. The "over-drag the list at scrollTop 0 grabs the sheet" handoff is deferred (needs iOS-Safari tuning); handle-drag/tap collapse ships first. The flag stays off until the mobile e2e is stable on the sheet.

Testing

  • pnpm typecheck clean · 1035 unit tests (new pure cores chat-trips.ts + itinerary.ts#sameIdSet) · next build clean (flagged + default) · eve info 0/0.
  • New Chromium-emulation mobile Playwright project + tests/e2e/helpers/pane.ts#openPane + plan-mobile.spec.ts (authed hydration gate — hydration.spec.ts runs anonymous and never renders the authed shell) + plan-phase3.spec.ts (dates / delete / persisted days / keyboard reorder / empty checklist). Full plan suite green in both projects against a seeded ephemeral Neo4j prod build; a flagged authed-hydration smoke confirmed the sheet renders clean.
  • A 6-dimension adversarial review ran after Phase 1; all confirmed findings (rename-wrong-trip, the two map-lifecycle races, the 768–780px grid collapse, the two-edits-per-turn staleness, the suggest_day_plan false flash) were fixed before merge.

Known follow-ups

The Phase-2 sheet is experimental (flag off): the scrollTop 0 gesture handoff and a shared-reveal-ref nicety in its second (hidden-on-desktop) TripBuilder instance are noted in ADR-076.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
trail-graph Ready Ready Preview, Comment Jul 7, 2026 8:24pm

Request Review

@johnymontana
johnymontana merged commit 42a338a into main Jul 7, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant