Skip to content

Export Route Flow (Pop-up & Warning)#175

Open
KesarSidhu wants to merge 19 commits into
benevolentbandwidth:mainfrom
KesarSidhu:step4-export-popup
Open

Export Route Flow (Pop-up & Warning)#175
KesarSidhu wants to merge 19 commits into
benevolentbandwidth:mainfrom
KesarSidhu:step4-export-popup

Conversation

@KesarSidhu
Copy link
Copy Markdown
Contributor

@KesarSidhu KesarSidhu commented May 18, 2026

Summary

  • Adds an Export flow on the Results page so dispatchers can download optimized routes as JSON files.
  • Adds an Edit Mode Active warning when export is attempted while editing or while a map pin move is still unsaved, so exported data matches what is on screen.

Motivation

  • Step 4 hi-fi includes exporting routes from Results; users need a way to save route data without leaving the page.
  • Exporting while edit mode is on (or while a dragged stop has not been saved) could produce JSON that does not match the current map or sidebar state. The warning blocks that path and tells the user to save or finish editing first.

Changes

Frontend (app/ui)

  • page.tsx: Export button in the results header; opens export modal or edit warning depending on state.
  • ExportRoutesModal.tsx: Modal to select routes (checkboxes, all selected by default), Export (N) for checked routes, Export All; per-route color accent in the list.
  • ExportEditWarningModal.tsx: Hi-fi warning when export is blocked — title “Edit Mode Active”, amber banner, Cancel / Done editing (exits edit mode).
  • downloadRouteJson.ts: Downloads one .json file per route (route_{n}_{driver-slug}.json), with a short stagger between files so browsers allow multiple downloads from one click.
  • routeColors.ts: Shared palette for route accent colors in the export list.
  • edit.module.css: --edit-teal-400 and --edit-foreground tokens for export modal styling.

Export behavior

  1. User clicks Export (disabled when there are no routes).
  2. If edit mode is on or there is an unsaved pin move (pendingPinMove), the Edit Mode Active modal appears instead of the export modal.
  3. Otherwise the Export Routes modal opens with all routes checked.
  4. User can toggle routes, then Export (N) or Export All; each selected route downloads as pretty-printed JSON of the full Route object (driver, stops, geometry, etc.).

Edit-mode warning behavior

  • Trigger: isEditMode === true or pendingPinMove != null (dragged marker not saved/cancelled).
  • Cancel / close / Escape / backdrop: dismiss warning only.
  • Done editing: turns off edit mode and clears any pending pin draft (same as leaving edit mode elsewhere); user can export again after saving any changes they still care about.
  • Does not auto-open the export modal after Done editing — user clicks Export again once ready.

Validation

Frontend

  • npm --prefix app/ui run lint
  • npm --prefix app/ui run format:check
  • npm --prefix app/ui run typecheck
  • npm --prefix app/ui run test
  • npm --prefix app/ui run build
  • Manual: /resultsExport opens modal; Export (N) / Export All download JSON
  • Manual: Edit on → Export shows Edit Mode Active warning (not export modal)
  • Manual: drag stop without saving → Export still shows warning; after Save + exit edit, export works

Backend

Skipped — frontend-only PR.

Risk

  • Low. Client-only downloads; no API or persistence changes.
  • Browsers may still prompt or limit multiple file downloads; stagger mitigates but is not guaranteed on every browser.
  • Done editing discards an unsaved pin drag (same as toggling edit off today); users with pending drags should use Save before exporting.

Rollout and Recovery

  • Ship with normal frontend deploy; no migrations or feature flags.
  • Rollback: revert PR; Export button and modals are removed with no data impact.

High-Signal PR Checklist

  • The summary states the user-visible or operational outcome, not just file names.
  • The motivation explains why this change is needed now.
  • The change list separates frontend work and describes export + warning behavior.
  • Validation includes exact commands run, relevant output, and any checks intentionally skipped.
  • Risks and rollback steps are called out.
  • Screenshots or request/response examples are included for UI and API behavior changes.

@KesarSidhu KesarSidhu force-pushed the step4-export-popup branch from b334a34 to c7d27bd Compare May 25, 2026 02:48
Copy link
Copy Markdown
Collaborator

@kirillakovalenko kirillakovalenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #175 — Export Route Flow (Pop-up & Warning)
Author: KesarSidhu | +1,180 / -264 across 19 files | Client-only

Overview
Adds an Export flow to the Results page: an Export button opens a routes selection modal (checkboxes, staggered multi-file download), and a blocking warning modal when edit mode or an unsaved pin drag is active. Also includes a visual redesign of the sidebar/stop cards, colored map markers per route, and a fixed SidebarResultsButton that now navigates to /results when stored routes exist.

Code Quality
Strengths:

Good test coverage for new utilities — recipientSummary.test.ts and new cases in vroomToRoutes.test.ts cover edge cases well.
useSyncExternalStore for useIsClient is the correct Next.js SSR-safe hydration pattern.
Accessibility is largely solid: aria-modal, aria-labelledby, useFocusTrap, aria-hidden on decorative SVGs, aria-current="page" on active nav item.
Blob URL is properly revoked after 1 s; stagger logic for multi-file download is pragmatic.
The SVG injection comment in markerSvgDataUrl (// fillColor is always a route palette hex...) is a good defensive note.
Issues
Blocking / High Priority

useIsClient duplicated (ExportEditWarningModal.tsx and ExportRoutesModal.tsx) — identical hook defined twice. Extract to app/results/hooks/useIsClient.ts or a shared location.

"Save" button always rendered (page.tsx:1528-1533) — The Cancel button is correctly gated on pendingPinMove != null, but the new Save button is always visible. This will confuse users who haven't started a drag. Both buttons should appear together or the Save should also be conditional:

{pendingPinMove != null && (
<>
Cancel
Save
</>
)}
--edit-stone-700 CSS variable (ExportRoutesModal.tsx:886) — The "Export (N)" button uses border-[var(--edit-stone-700)] but this variable isn't added in the edit.module.css diff. If it was already defined elsewhere, this is fine — but it should be verified. A broken CSS variable silently renders as border-transparent.

Typecheck and tests not run — The PR checklist leaves typecheck, test, and build unchecked. For a TypeScript PR with new types and CSS variables, these are important to confirm before merge.

Medium Priority

isSidebarOpen hardcoded to true (page.tsx:1426) — The sidebar toggle button is removed and the state replaced with a constant. This eliminates the ability to collapse the sidebar on narrow screens. If this is a deliberate hi-fi decision for now, a // always open for now comment would clarify intent.

role="status" on warning banner (ExportEditWarningModal.tsx:634) — The amber warning block uses role="status" (a polite live region). For a blocking warning, role="alert" is more appropriate (announces immediately) and aligns better with "unable to export while editing".

Sequence badge uses hardcoded bg-cyan-500 (Sidebar.tsx:1343) — Stop sequence numbers and connector lines use a fixed cyan rather than the per-route accent color. This looks inconsistent with the rest of the card where accent is used for the route icon. Intentional or oversight?

Minor

Module-level mutable markerScaledSize (Map.tsx:913) — The singleton pattern with a module-level let is fine for production but will leak state between tests if the Maps module is ever unit tested.

capacityUsed double condition (vroomToRoutes.ts:228) — address?.deliveryQuantity && address.deliveryQuantity > 0 checks the same value twice. Cleaner: (address?.deliveryQuantity ?? 0) > 0.

downloadRoutesAsJsonFiles no error handling (downloadRouteJson.ts:21) — If URL.createObjectURL or DOM operations fail, the error is silently dropped. A try/catch with a console.error at minimum would surface browser-blocked downloads.

Summary
The core export logic is well-structured and the accessibility/SSR patterns are correct. Fix the always-visible Save button (#2) and verify --edit-stone-700 (#3) before merge, and run typecheck/build (#4). The duplicated useIsClient (#1) is a clean-up item. Low risk overall given the PR is client-only with no API or persistence changes.

Kesar Sidhu and others added 19 commits May 27, 2026 12:01
Align the results header with the mock by using a compact app label plus Save/Export actions while preserving the existing sidebar toggle and pending-edit cancel behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Refine the sidebar title section, edit action, and footer copy styling to better match the hifi mock while keeping the route list behavior unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use route palette colors for map pins and switch marker visuals from plain dots to pin-style markers so map markers align with route styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
Match the edit-mode layout with a wider results panel, save-edits CTA styling, and centered map status banner while keeping existing route behavior intact.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add cyan timeline markers with dotted connectors in expanded routes.
Remove duplicate sequence badge from EditableStopItem.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract recipientSummary for tests, hide locked contact row when empty,
and use middle-dot formatting on hi-fi address cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
Map recipient_phone CSV alias, prefer form delivery quantity for
capacityUsed, and add unit tests for recipient fields and capacity fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add optional delivery window fields on Stop, format recipient and
delivery lines on EditableStopItem, and map form delivery times into routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Match hi-fi expanded stop cards with icon rows for recipient and
delivery, route-tinted package pill, and rounded card layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add note doc icon row, hi-fi read-only note panel, and teal save
control in edit mode for expanded stop cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire hi-fi results Export button to route JSON downloads, with a
warning when edit mode or pending pin moves are active. Rebased on
step3-edit-look-3-main-v7; keep existing routeColors palette.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@KesarSidhu KesarSidhu force-pushed the step4-export-popup branch from 39bcd68 to fbf3da1 Compare May 27, 2026 19:02
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.

2 participants