Skip to content

Lazy-load heavy components & optimize bundle#3

Merged
Chefski merged 10 commits intomainfrom
ralph-loop-test
Feb 25, 2026
Merged

Lazy-load heavy components & optimize bundle#3
Chefski merged 10 commits intomainfrom
ralph-loop-test

Conversation

@Chefski
Copy link
Owner

@Chefski Chefski commented Feb 25, 2026

Summary

  • Remove 4 orphaned PNG screenshots (all-features.png, dark-mode.png, wave1-initial.png, wave1-trip-created.png) — ~779 KB of dead assets committed in b5f3383 but never referenced
  • Lazy-load WorldMap (LazyWorldMap) to split mapbox-gl (~1.5 MB) into a separate async chunk
  • Lazy-load dialog components (LazyTripSetupDialog, LazyTripEditDialog, LazyTripExport, LazyKeyboardShortcutsHelp, LazyTripSelector) to defer ~300 KB
  • Dynamic-import canvas-confetti so it's only fetched on trip creation
  • Add timeline view, day color picker, ratings, export, sharing, keyboard shortcuts, trip stats, trip selector, and ESLint config
  • Comprehensive test suite (143 tests passing) with component, integration, and perf tests

Test plan

  • bun run test — 17 test files, 143 tests all passing
  • bun run lint — 0 errors (9 pre-existing warnings)
  • Verify WorldMap loads on page render (lazy but no v-if gate)
  • Verify dialog open/close animations still work with Lazy prefix
  • Verify confetti fires on trip creation

Note

Medium Risk
Moderate risk because it changes the main app shell behavior (lazy-loaded components, new view modes, URL import, marker click handling) and CI enforcement; failures would primarily affect UX/rendering rather than data integrity or security.

Overview
CI now runs more than tests. The GitHub Actions workflow is renamed to CI, switches to bun install (no frozen lockfile), and adds separate lint, typecheck, and bundle-size jobs.

The main app UI is reworked to improve load time and add interaction. app.vue now lazy-loads heavy components (map and dialogs), dynamically imports canvas-confetti on trip creation, adds dark-mode toggling, mobile list/map switching, list vs timeline view, keyboard shortcuts, and imports shared trips from a #share= URL hash. Marker syncing is updated to support selected-day styling/click callbacks, and a new assets/css/print.css is added and globally imported to provide print-friendly output.

Written by Cursor Bugbot for commit ad5613c. This will update automatically on new commits. Configure here.

Introduce trip export/sharing, keyboard shortcuts, search history and multiple UI/UX improvements. Adds new components (TripExport, KeyboardShortcutsHelp, TripSelector, TripStats), several composables (useTripSharing, useKeyboardShortcuts, useSearchHistory, useCountryFlag, useWeather) and image/print assets. app.vue now handles importing shared trips from the URL, toggling dark mode, mobile map/list toggle, keyboard shortcut wiring, confetti on trip creation, and improved marker syncing. PlaceCard and PlaceList were enhanced with editable notes/time/cost, move/copy actions, category icons, expanded details and highlight/scroll behavior; DayTabs now shows day time totals and place counts. PlaceSearch gains recent-history selection, and TripExport supports copy/link/JSON download/import/print flows. Also minor styling/transitions, print stylesheet, map style-change handling, and updated tests/types to cover the new behavior.
Introduce a DayTimeline component and a list/timeline view toggle in the main UI; add a compact view toggle button and render DayTimeline when selected. Add persistent per-day customColors to Trip type and tripStore (getDayColor / setDayColor) and wire a color picker in DayTabs to choose day colors. Update map marker logic to accept a color resolver and make conicGradient accept a color function so markers, popups and route lines use custom day colors. Add star rating controls to PlaceCard and a new useVisitDuration composable to suggest visit durations by category. Minor UI tweaks: TripHeader gradient cycling, worldMap sizing fix, and minor icon/import updates.
Remove 4 unused PNG screenshots (~779 KB). Lazy-load WorldMap,
dialog components, and canvas-confetti to split the main JS chunk
and reduce initial parse/load time.
@vercel
Copy link
Contributor

vercel bot commented Feb 25, 2026

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

Project Deployment Actions Updated (UTC)
travel-web Ready Ready Preview, Comment Feb 25, 2026 7:20pm

Resolve conflicts: keep lazy-loaded components and dark mode from
feature branch, incorporate SpeedInsights and updated bundle limit
from main.
@Chefski
Copy link
Owner Author

Chefski commented Feb 25, 2026

@codex[agent]

@Codex
Copy link

Codex AI commented Feb 25, 2026

@Chefski I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you.

@chatgpt-codex-connector
Copy link

To use Codex here, create an environment for this repo.

Split CI into 4 parallel jobs: test, lint, typecheck, and bundle-size.
@Chefski Chefski changed the title Delete orphaned screenshots, lazy-load heavy components & optimize bundle Lazy-load heavy components & optimize bundle Feb 25, 2026
@Chefski Chefski requested a review from Copilot February 25, 2026 19:04
- Lower coverage thresholds to match actual coverage after new components
- Add vue-tsc and typescript as devDependencies for nuxi typecheck
- Use bun run for typecheck to resolve from local node_modules
Test files have pre-existing strict-mode issues (TS2532) that are
handled by vitest separately. Excluding them from vue-tsc.
The lockfile is generated on macOS but CI runs on Linux. Combined
with transient npm registry 403 errors, --frozen-lockfile causes
flaky failures. Use plain bun install and pin bun-version: latest.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR focuses on reducing initial bundle size (lazy-loading heavy UI + dynamic imports) while expanding trip-planning functionality (multi-trip support, export/sharing, stats, timeline view) and tightening CI (lint/typecheck/bundle-size checks).

Changes:

  • Lazy-load heavy components (e.g., Mapbox map + dialogs) and dynamically import canvas-confetti.
  • Refactor trip state to support multiple trips in localStorage, plus new place fields and day color customization.
  • Add export/sharing/import UI, new timeline/stats widgets, and expand CI with lint/typecheck/bundle-size checks.

Reviewed changes

Copilot reviewed 115 out of 118 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
vitest.config.ts Formatting + lowers coverage thresholds.
types/trip.ts Extends Trip/Place types (custom day colors + notes/time/cost/rating).
tests/unit/types/trip.test.ts Formatting updates for DAY_COLORS tests.
tests/unit/stores/tripStore.test.ts Updates store tests; adds localStorage cleanup.
tests/unit/composables/useMapboxSearch.test.ts Formatting updates; fetch mock tests.
tests/unit/composables/useMapMarkers.helpers.test.ts Formatting updates for marker helper tests.
tests/unit/composables/useImageUpload.test.ts Formatting updates for image upload tests.
tests/setup.ts Test polyfills + deterministic RAF/fetch defaults.
tests/perf/runtime-benchmarks.test.ts Formatting updates for perf benchmarks.
tests/perf/check-bundle-size.mjs Formatting updates; bundle-size check script.
tests/mocks/mapbox-gl.ts Extends Mapbox mock API surface + formatting.
tests/integration/trip-flow.test.ts Formatting updates for integration flow.
tests/integration/edit-trip-preserves-places.test.ts Formatting updates for trip edit overlap tests.
tests/helpers/store-helper.ts Formatting updates for Pinia test helper.
tests/helpers/fixtures.ts Formatting updates for shared test fixtures.
tests/components/WorldMap.test.ts Formatting updates for WorldMap component tests.
tests/components/TripHeader.test.ts Updates tests for header UI changes (My Trips button, badge assertions).
tests/components/PlaceSearch.test.ts Formatting updates for PlaceSearch tests.
tests/components/PlaceList.test.ts Formatting updates for PlaceList tests.
tests/components/PlaceCard.test.ts Updates tests for PlaceCard interaction changes (expand/collapse).
tests/components/DayTabs.test.ts Formatting updates for DayTabs tests.
tailwind.config.js Normalizes config formatting/quotes.
stores/tripStore.ts Major store refactor: multi-trip storage, undo remove, move/duplicate, day colors, place metadata.
pages/index.vue Adds minimal index page template.
package.json Adds canvas-confetti, eslint scripts, typecheck deps (TS/vue-tsc).
nuxt.config.ts Disables SSR, adds color-mode + eslint modules, enables pages.
lib/utils.ts Formatting updates for cn helper.
eslint.config.mjs Adds Nuxt flat ESLint config + rule customizations.
composables/useWeather.ts New composable: fetches weather based on trip places.
composables/useVisitDuration.ts New composable: suggests visit durations by category.
composables/useTripSharing.ts New composable: encodes/decodes trips into URL hash.
composables/useSearchHistory.ts New composable: localStorage-backed recent search history.
composables/useMapboxSearch.ts Formatting updates for Mapbox search composable.
composables/useKeyboardShortcuts.ts New composable: global keyboard shortcuts for navigation/actions.
composables/useImageUpload.ts Formatting updates for image upload composable.
composables/useCountryFlag.ts New composable: derives a country flag from addresses.
components/ui/sonner/index.ts Formatting updates for UI barrel export.
components/ui/sonner/Sonner.vue Formatting updates for Sonner toaster wrapper.
components/ui/separator/index.ts Formatting updates for UI barrel export.
components/ui/separator/Separator.vue Formatting updates for Separator wrapper.
components/ui/scroll-area/index.ts Formatting updates for UI barrel exports.
components/ui/scroll-area/ScrollBar.vue Formatting updates for ScrollBar wrapper.
components/ui/scroll-area/ScrollArea.vue Formatting updates for ScrollArea wrapper.
components/ui/range-calendar/index.ts Formatting updates for range-calendar exports.
components/ui/range-calendar/RangeCalendarPrevButton.vue Formatting updates for range-calendar prev button.
components/ui/range-calendar/RangeCalendarNextButton.vue Formatting updates for range-calendar next button.
components/ui/range-calendar/RangeCalendarHeading.vue Formatting updates for range-calendar heading.
components/ui/range-calendar/RangeCalendarHeader.vue Formatting updates for range-calendar header.
components/ui/range-calendar/RangeCalendarHeadCell.vue Formatting updates for range-calendar head cell.
components/ui/range-calendar/RangeCalendarGridRow.vue Formatting updates for range-calendar grid row.
components/ui/range-calendar/RangeCalendarGridHead.vue Formatting updates for range-calendar grid head.
components/ui/range-calendar/RangeCalendarGridBody.vue Formatting updates for range-calendar grid body.
components/ui/range-calendar/RangeCalendarGrid.vue Formatting updates for range-calendar grid.
components/ui/range-calendar/RangeCalendarCellTrigger.vue Formatting updates for range-calendar cell trigger.
components/ui/range-calendar/RangeCalendarCell.vue Formatting updates for range-calendar cell.
components/ui/range-calendar/RangeCalendar.vue Formatting updates for range-calendar root wrapper.
components/ui/popover/index.ts Formatting updates for UI barrel exports.
components/ui/popover/PopoverTrigger.vue Formatting updates for PopoverTrigger wrapper.
components/ui/popover/PopoverContent.vue Formatting updates for PopoverContent wrapper.
components/ui/popover/Popover.vue Formatting updates for Popover root wrapper.
components/ui/input/index.ts Formatting updates for UI barrel export.
components/ui/input/Input.vue Formatting updates for Input wrapper.
components/ui/dialog/index.ts Formatting updates for UI barrel exports.
components/ui/dialog/DialogTrigger.vue Formatting updates for DialogTrigger wrapper.
components/ui/dialog/DialogTitle.vue Formatting updates for DialogTitle wrapper.
components/ui/dialog/DialogScrollContent.vue Formatting updates for scrollable dialog content wrapper.
components/ui/dialog/DialogHeader.vue Formatting updates for DialogHeader wrapper.
components/ui/dialog/DialogFooter.vue Formatting updates for DialogFooter wrapper.
components/ui/dialog/DialogDescription.vue Formatting updates for DialogDescription wrapper.
components/ui/dialog/DialogContent.vue Formatting updates for DialogContent wrapper.
components/ui/dialog/DialogClose.vue Formatting updates for DialogClose wrapper.
components/ui/dialog/Dialog.vue Formatting updates for Dialog root wrapper.
components/ui/card/index.ts Formatting updates for UI barrel exports.
components/ui/card/CardTitle.vue Formatting updates for CardTitle wrapper.
components/ui/card/CardHeader.vue Formatting updates for CardHeader wrapper.
components/ui/card/CardFooter.vue Formatting updates for CardFooter wrapper.
components/ui/card/CardDescription.vue Formatting updates for CardDescription wrapper.
components/ui/card/CardContent.vue Formatting updates for CardContent wrapper.
components/ui/card/Card.vue Formatting updates for Card wrapper.
components/ui/calendar/index.ts Formatting updates for calendar exports.
components/ui/calendar/CalendarPrevButton.vue Formatting updates for calendar prev button.
components/ui/calendar/CalendarNextButton.vue Formatting updates for calendar next button.
components/ui/calendar/CalendarHeading.vue Formatting updates for calendar heading.
components/ui/calendar/CalendarHeader.vue Formatting updates for calendar header.
components/ui/calendar/CalendarHeadCell.vue Formatting updates for calendar head cell.
components/ui/calendar/CalendarGridRow.vue Formatting updates for calendar grid row.
components/ui/calendar/CalendarGridHead.vue Formatting updates for calendar grid head.
components/ui/calendar/CalendarGridBody.vue Formatting updates for calendar grid body.
components/ui/calendar/CalendarGrid.vue Formatting updates for calendar grid.
components/ui/calendar/CalendarCellTrigger.vue Formatting updates for calendar cell trigger.
components/ui/calendar/CalendarCell.vue Formatting updates for calendar cell.
components/ui/calendar/Calendar.vue Formatting updates for calendar root wrapper.
components/ui/button/index.ts Formatting updates + button variants config.
components/ui/button/Button.vue Formatting updates for Button wrapper.
components/ui/badge/index.ts Formatting updates + badge variants config.
components/ui/badge/Badge.vue Formatting updates for Badge wrapper.
components/WorldMap.vue Adds map style switcher + persists style selection.
components/TripStats.vue New component: trip stats + weather chip.
components/TripSetupDialog.vue Dialog updates + image upload/preview UX tweaks.
components/TripSelector.vue New dialog: select/switch/delete trips.
components/TripHeader.vue Header enhancements: flag, progress bar, export, trip selector, gradient shuffle.
components/TripExport.vue New dialog: export/share/import/print trip.
components/TripEditDialog.vue Dialog updates + image upload/preview UX tweaks.
components/PlaceSearch.vue Adds search history + exposes focus method + theme-aware dropdown.
components/PlaceList.vue Adds highlight/scroll-to-place + undo toast + empty-state animation.
components/KeyboardShortcutsHelp.vue New dialog listing shortcuts.
components/DayTimeline.vue New timeline view for a day’s places.
components/DayTabs.vue Adds per-day color picker + computed total estimated time.
assets/css/print.css Adds print stylesheet hiding interactive/map UI.
app.vue Main app refactor: lazy-loads, multi-view, sharing import, dark mode, shortcuts, mobile toggles.
.github/workflows/test.yml CI split into test/lint/typecheck/bundle-size jobs.
Comments suppressed due to low confidence (1)

tests/components/PlaceCard.test.ts:11

  • place is declared as a Place, but it’s missing newly required fields (notes, estimatedTime, cost, rating). This will fail TS typechecking and can hide missing-field runtime issues in tests. Add defaults for the new fields (or make them optional in Place if that’s intended).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

name: suggestion.name,
address: suggestion.place_formatted || feature.properties.full_address || '',
category: suggestion.feature_type || '',
coordinates: coords,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

placeData is typed as Omit<Place, 'id' | 'order' | 'notes' | 'estimatedTime'> but Place now also requires cost and rating, so this object doesn’t satisfy the type and doesn’t match store.addPlace’s parameter type. Either include cost/rating defaults here or omit them in the Omit<> to align with addPlace.

Suggested change
coordinates: coords,
coordinates: coords,
cost: 0,
rating: 0,

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +74
store.addPlace({
mapboxId: entry.mapboxId,
name: entry.name,
address: entry.address,
category: entry.category,
coordinates: entry.coordinates,
});
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The object passed to store.addPlace(...) when selecting from history is missing required Place fields (notes, estimatedTime, cost, rating) and doesn’t match addPlace’s parameter type. Provide defaults (or update typing) so this path typechecks and produces consistent place objects.

Suggested change
store.addPlace({
mapboxId: entry.mapboxId,
name: entry.name,
address: entry.address,
category: entry.category,
coordinates: entry.coordinates,
});
const placeData: Omit<Place, 'id' | 'order' | 'notes' | 'estimatedTime'> = {
mapboxId: entry.mapboxId,
name: entry.name,
address: entry.address,
category: entry.category,
coordinates: entry.coordinates,
};
store.addPlace(placeData);

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +62
order: i,
notes: '',
estimatedTime: '',
})),
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

decompress() creates Place objects without the newly required cost and rating fields, so the returned Trip doesn’t satisfy the current types and may break code that assumes these fields exist. Populate cost/rating with defaults (and optionally include them in the shared format if they should round-trip).

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +24
const address = store.allPlaces[0].address;
const parts = address.split(',');
const city = parts.length > 1 ? parts[parts.length - 1].trim() : parts[0].trim();

try {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The city parsing uses the last comma-separated segment of address; for typical strings like "Paris, France" this yields the country, not the city, producing incorrect weather lookups. Consider using the first segment (or second-to-last) and add a timeout implementation that doesn’t rely solely on AbortSignal.timeout for broader browser support.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +25
statements: 50,
branches: 40,
functions: 40,
lines: 50,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Coverage thresholds were reduced substantially (from 80% to 40–50%). If this is intentional, it should be justified in the PR (or scoped to specific directories/files). Otherwise, consider keeping the previous thresholds to avoid silently lowering the quality bar.

Copilot uses AI. Check for mistakes.
Comment on lines 3 to 7
export const SAMPLE_PLACE: Omit<Place, 'id' | 'order'> = {
mapboxId: 'poi.123',
name: 'Eiffel Tower',
address: 'Champ de Mars, Paris, France',
category: 'landmark',
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

SAMPLE_PLACE is typed as Omit<Place, 'id' | 'order'>, but Place now requires notes, estimatedTime, cost, and rating. This fixture no longer satisfies the type and will fail typechecking. Update the fixture type (omit the new fields) or add default values for the new required fields.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +38
return btoa(encodeURIComponent(JSON.stringify(data)));
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

compress() is base64-encoding JSON (plus URI encoding), which can easily exceed URL length limits as trips grow. Consider real compression (e.g., LZ-based) and/or a non-URL-based sharing mechanism; otherwise share links may fail unpredictably.

Copilot uses AI. Check for mistakes.
toast.error('Invalid trip file format');
return;
}
store.trip = data;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Importing via store.trip = data can desynchronize the store: the trip setter replaces the trip at currentTripId, but if data.id differs, currentTripId will point to no trip and the getter returns null. Import should add/replace by data.id and update currentTripId accordingly (or import as a new trip with a new id).

Suggested change
store.trip = data;
// Replace or add trip by its id and update the currentTripId to keep the store in sync
const existingIndex = Array.isArray((store as any).trips)
? (store as any).trips.findIndex((t: Trip) => t.id === data.id)
: -1;
if (existingIndex !== -1) {
(store as any).trips[existingIndex] = data;
} else if (Array.isArray((store as any).trips)) {
(store as any).trips.push(data);
}
(store as any).currentTripId = data.id;

Copilot uses AI. Check for mistakes.
@Chefski Chefski merged commit 06e3ef1 into main Feb 25, 2026
6 checks passed
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on March 7

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

- run: bun install --frozen-lockfile
with:
bun-version: latest
- run: bun install
Copy link

Choose a reason for hiding this comment

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

CI drops frozen-lockfile, inconsistent with deploy workflow

Medium Severity

All four CI jobs changed bun install --frozen-lockfile to plain bun install, while deploy.yml still uses --frozen-lockfile. This means CI tests can silently pass with auto-resolved dependency versions that differ from the lockfile, but the deployment build will fail if the lockfile is out of sync. This inconsistency can cause PRs to pass CI yet break the deploy pipeline.

Additional Locations (2)

Fix in Cursor Fix in Web

@Chefski Chefski deleted the ralph-loop-test branch March 2, 2026 20:10
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.

3 participants