diff --git a/client/index.html b/client/index.html index 9578ebd20..d31606d03 100644 --- a/client/index.html +++ b/client/index.html @@ -19,10 +19,7 @@ - - - - +
diff --git a/client/package.json b/client/package.json index cb9755494..5881a24d8 100644 --- a/client/package.json +++ b/client/package.json @@ -7,6 +7,7 @@ "dev": "vite", "prebuild": "node scripts/generate-icons.mjs", "build": "vite build", + "build:analyze": "vite build --mode analyze", "preview": "vite preview", "typecheck": "tsc --noEmit", "test": "vitest run", @@ -28,8 +29,8 @@ }, "dependencies": { "@fontsource/geist-sans": "^5.2.5", + "@fontsource/museomoderno": "^5.3.0", "@fontsource/poppins": "^5.2.7", - "@react-pdf/renderer": "^4.5.1", "@simplewebauthn/browser": "^13.1.2", "@trek/shared": "*", "axios": "^1.6.7", @@ -50,7 +51,6 @@ "react-leaflet-cluster": "^4.1.3", "react-markdown": "^10.1.0", "react-router-dom": "^6.22.2", - "react-window": "^2.2.7", "rehype-sanitize": "^6.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", @@ -70,7 +70,6 @@ "@types/node": "^25.9.3", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", - "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.9", "autoprefixer": "^10.4.18", @@ -86,6 +85,7 @@ "prettier": "^3.8.3", "prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-tailwindcss": "^0.8.0", + "rollup-plugin-visualizer": "^7.0.1", "sharp": "^0.35.0", "tailwindcss": "^3.4.1", "typescript": "^6.0.2", diff --git a/client/src/components/Settings/MapSettingsTab.test.tsx b/client/src/components/Settings/MapSettingsTab.test.tsx index 79e1934da..706a45b13 100644 --- a/client/src/components/Settings/MapSettingsTab.test.tsx +++ b/client/src/components/Settings/MapSettingsTab.test.tsx @@ -183,7 +183,8 @@ describe('MapSettingsTab – GL providers', () => { expect(screen.getByText('Mapbox Access Token')).toBeInTheDocument(); expect(screen.getByText('Map Style')).toBeInTheDocument(); expect(screen.queryByText('Map Template')).not.toBeInTheDocument(); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-provider', 'mapbox-gl'); + // The preview is lazy-loaded, so the first read of it has to await the chunk. + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-provider', 'mapbox-gl'); expect(screen.getByDisplayValue(MAPBOX_STANDARD)).toBeInTheDocument(); }); @@ -240,7 +241,7 @@ describe('MapSettingsTab – GL providers', () => { expect(screen.queryByText('3D Buildings & Terrain')).not.toBeInTheDocument(); expect(screen.getByDisplayValue(OFM_LIBERTY)).toBeInTheDocument(); expect(screen.getByText('Preset or OpenFreeMap style URL. OpenFreeMap styles work without a token.')).toBeInTheDocument(); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-provider', 'maplibre-gl'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-provider', 'maplibre-gl'); }); it('FE-COMP-MAP-023: switching from MapLibre back to Mapbox drops the OpenFreeMap style', async () => { @@ -274,7 +275,7 @@ describe('MapSettingsTab – GL providers', () => { await user.type(screen.getByPlaceholderText('pk.eyJ1Ijoi...'), 'pk.token'); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-token', 'pk.token'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-token', 'pk.token'); }); it('FE-COMP-MAP-026: the 3D and quality toggles drive the preview', async () => { @@ -282,6 +283,7 @@ describe('MapSettingsTab – GL providers', () => { render(); await user.click(screen.getByText('Mapbox GL')); + await screen.findByTestId('gl-preview'); const preview = () => screen.getByTestId('gl-preview'); expect(preview()).toHaveAttribute('data-3d', 'true'); expect(preview()).toHaveAttribute('data-quality', 'false'); diff --git a/client/src/components/Settings/MapSettingsTab.tsx b/client/src/components/Settings/MapSettingsTab.tsx index ae4c30612..b7ae3fd23 100644 --- a/client/src/components/Settings/MapSettingsTab.tsx +++ b/client/src/components/Settings/MapSettingsTab.tsx @@ -1,11 +1,14 @@ -import React, { useState, useEffect, useMemo, useRef } from 'react' +import React, { useState, useEffect, useMemo, useRef, lazy, Suspense } from 'react' import { Map, Save, Layers, Box, ChevronDown, Check, Globe2 } from 'lucide-react' import { useTranslation } from '../../i18n' import { useSettingsStore } from '../../store/settingsStore' import { useToast } from '../shared/Toast' import CustomSelect from '../shared/CustomSelect' import { MapView } from '../Map/MapView' -import GlMapPreview from './MapboxPreview' +// The preview is the only place that imports both GL engines eagerly, which put +// them in the entry chunk and cancelled out the lazy split in MapViewAuto. Load +// it on demand so a Leaflet-only install never pays for them. +const GlMapPreview = lazy(() => import('./MapboxPreview')) import Section from './Section' import ToggleSwitch from './ToggleSwitch' import type { Place } from '../../types' @@ -399,18 +402,20 @@ export default function MapSettingsTab(): React.ReactElement {
{provider !== 'leaflet' ? ( - + }> + + ) : ( // eslint-disable-next-line @typescript-eslint/no-explicit-any React.createElement(MapView as any, { diff --git a/client/src/main.tsx b/client/src/main.tsx index c4f026869..60cea2ff8 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -9,6 +9,11 @@ import '@fontsource/poppins/400.css' import '@fontsource/poppins/500.css' import '@fontsource/poppins/600.css' import '@fontsource/poppins/700.css' +// MuseoModerno for the login wordmark — same reasoning, it used to come from the +// Google Fonts CDN via a render-blocking in index.html. +import '@fontsource/museomoderno/400.css' +import '@fontsource/museomoderno/700.css' +import '@fontsource/museomoderno/800.css' // Geist Sans (self-hosted too) — used only for secondary "subtext" via --font-subtext. import '@fontsource/geist-sans/400.css' import '@fontsource/geist-sans/500.css' diff --git a/client/src/mobile/screens/settings/MSettingsMap.tsx b/client/src/mobile/screens/settings/MSettingsMap.tsx index df5714b12..e8bf5bda8 100644 --- a/client/src/mobile/screens/settings/MSettingsMap.tsx +++ b/client/src/mobile/screens/settings/MSettingsMap.tsx @@ -1,10 +1,12 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useEffect, useMemo, useState, lazy, Suspense } from 'react' import { Box, Check, ChevronDown, Globe2, Layers, Map, Save } from 'lucide-react' import { useTranslation } from '../../../i18n' import { useSettingsStore } from '../../../store/settingsStore' import { useToast } from '../../../components/shared/Toast' import { MapView } from '../../../components/Map/MapView' -import GlMapPreview from '../../../components/Settings/MapboxPreview' +// Lazy for the same reason as the desktop map settings tab: this import is what +// dragged both GL engines into the entry chunk. +const GlMapPreview = lazy(() => import('../../../components/Settings/MapboxPreview')) import type { Place } from '../../../types' import { MAPBOX_DEFAULT_STYLE, @@ -249,16 +251,18 @@ export default function MSettingsMap() {
{provider !== 'leaflet' ? ( - + }> + + ) : ( // eslint-disable-next-line @typescript-eslint/no-explicit-any React.createElement(MapView as any, { diff --git a/client/src/pages/RegisterPage.test.tsx b/client/src/pages/RegisterPage.test.tsx deleted file mode 100644 index bea7c95a4..000000000 --- a/client/src/pages/RegisterPage.test.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { render, screen, waitFor } from '../../tests/helpers/render'; -import userEvent from '@testing-library/user-event'; -import { http, HttpResponse } from 'msw'; -import { server } from '../../tests/helpers/msw/server'; -import { resetAllStores } from '../../tests/helpers/store'; -import RegisterPage from './RegisterPage'; - -const mockNavigate = vi.fn(); -vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { ...actual, useNavigate: () => mockNavigate }; -}); - -const USERNAME_PLACEHOLDER = 'johndoe'; -const EMAIL_PLACEHOLDER = 'your@email.com'; -const PASSWORD_PLACEHOLDER = 'Min. 6 characters'; -const CONFIRM_PASSWORD_PLACEHOLDER = 'Repeat password'; - -beforeEach(() => { - resetAllStores(); - vi.clearAllMocks(); -}); - -describe('RegisterPage', () => { - describe('FE-PAGE-REG-001: Renders registration form with all fields', () => { - it('shows username, email, password, confirm-password inputs and submit button', () => { - render(); - expect(screen.getByPlaceholderText(USERNAME_PLACEHOLDER)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(EMAIL_PLACEHOLDER)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /register/i })).toBeInTheDocument(); - }); - }); - - describe('FE-PAGE-REG-002: Password mismatch shows error', () => { - it('displays mismatch error without calling API', async () => { - const user = userEvent.setup(); - render(); - - await user.type(screen.getByPlaceholderText(USERNAME_PLACEHOLDER), 'testuser'); - await user.type(screen.getByPlaceholderText(EMAIL_PLACEHOLDER), 'test@example.com'); - await user.type(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER), 'password1'); - await user.type(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER), 'password2'); - await user.click(screen.getByRole('button', { name: /^register$/i })); - - await waitFor(() => { - expect(screen.getByText(/do not match/i)).toBeInTheDocument(); - }); - }); - }); - - describe('FE-PAGE-REG-003: Password too short shows error', () => { - it('displays length error when passwords are the same but too short', async () => { - const user = userEvent.setup(); - render(); - - await user.type(screen.getByPlaceholderText(USERNAME_PLACEHOLDER), 'testuser'); - await user.type(screen.getByPlaceholderText(EMAIL_PLACEHOLDER), 'test@example.com'); - await user.type(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER), 'abc'); - await user.type(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER), 'abc'); - await user.click(screen.getByRole('button', { name: /^register$/i })); - - await waitFor(() => { - expect(screen.getByText(/at least 8/i)).toBeInTheDocument(); - }); - }); - }); - - describe('FE-PAGE-REG-004: Successful registration navigates to /dashboard', () => { - it('calls navigate("/dashboard") after successful registration', async () => { - const user = userEvent.setup(); - render(); - - await user.type(screen.getByPlaceholderText(USERNAME_PLACEHOLDER), 'testuser'); - await user.type(screen.getByPlaceholderText(EMAIL_PLACEHOLDER), 'test@example.com'); - await user.type(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER), 'password123'); - await user.type(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER), 'password123'); - await user.click(screen.getByRole('button', { name: /^register$/i })); - - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith('/dashboard'); - }); - }); - }); - - describe('FE-PAGE-REG-005: Loading state during submission', () => { - it('disables submit button and shows loading text while registering', async () => { - server.use( - http.post('/api/auth/register', async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - return HttpResponse.json({ user: { id: 1, username: 'newuser' } }); - }), - ); - - const user = userEvent.setup(); - render(); - - await user.type(screen.getByPlaceholderText(USERNAME_PLACEHOLDER), 'testuser'); - await user.type(screen.getByPlaceholderText(EMAIL_PLACEHOLDER), 'test@example.com'); - await user.type(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER), 'password123'); - await user.type(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER), 'password123'); - await user.click(screen.getByRole('button', { name: /^register$/i })); - - await waitFor(() => { - const btn = screen.getByRole('button', { name: /registering/i }); - expect(btn).toBeDisabled(); - }); - }); - }); - - describe('FE-PAGE-REG-006: API error displayed', () => { - it('shows error message returned by the API', async () => { - server.use( - http.post('/api/auth/register', () => { - return HttpResponse.json({ error: 'Username already taken' }, { status: 409 }); - }), - ); - - const user = userEvent.setup(); - render(); - - await user.type(screen.getByPlaceholderText(USERNAME_PLACEHOLDER), 'testuser'); - await user.type(screen.getByPlaceholderText(EMAIL_PLACEHOLDER), 'test@example.com'); - await user.type(screen.getByPlaceholderText(PASSWORD_PLACEHOLDER), 'password123'); - await user.type(screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER), 'password123'); - await user.click(screen.getByRole('button', { name: /^register$/i })); - - await waitFor(() => { - expect(screen.getByText('Username already taken')).toBeInTheDocument(); - }); - }); - }); - - describe('FE-PAGE-REG-007: Show/hide password toggle', () => { - it('toggles password input type between password and text', async () => { - const user = userEvent.setup(); - render(); - - const passwordInput = screen.getByPlaceholderText(PASSWORD_PLACEHOLDER); - const confirmInput = screen.getByPlaceholderText(CONFIRM_PASSWORD_PLACEHOLDER); - - expect(passwordInput).toHaveAttribute('type', 'password'); - expect(confirmInput).toHaveAttribute('type', 'password'); - - // The toggle button is the only button of type "button" (not submit) before form submission - const toggleButton = screen.getByRole('button', { name: '' }); - await user.click(toggleButton); - - expect(passwordInput).toHaveAttribute('type', 'text'); - expect(confirmInput).toHaveAttribute('type', 'text'); - - await user.click(toggleButton); - - expect(passwordInput).toHaveAttribute('type', 'password'); - expect(confirmInput).toHaveAttribute('type', 'password'); - }); - }); - - describe('FE-PAGE-REG-008: Link to login page is present', () => { - it('renders a Sign In link pointing to /login', () => { - render(); - const link = screen.getByRole('link', { name: /sign in/i }); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', '/login'); - }); - }); - - describe('FE-PAGE-REG-009: Feature list rendered', () => { - it('renders feature list items in the DOM', () => { - render(); - // Features are always in the DOM (hidden via CSS on mobile) - expect(screen.getByText(/Unlimited trip plans/i)).toBeInTheDocument(); - expect(screen.getByText(/Interactive map view/i)).toBeInTheDocument(); - expect(screen.getByText(/Track reservations/i)).toBeInTheDocument(); - }); - }); - - describe('FE-PAGE-REG-010: Required attribute on username input', () => { - it('username input has required attribute', () => { - render(); - expect(screen.getByPlaceholderText(USERNAME_PLACEHOLDER)).toBeRequired(); - }); - }); -}); diff --git a/client/src/pages/RegisterPage.tsx b/client/src/pages/RegisterPage.tsx deleted file mode 100644 index 1807f1401..000000000 --- a/client/src/pages/RegisterPage.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react' -import { Link } from 'react-router-dom' -import { useTranslation } from '../i18n' -import { Map, Eye, EyeOff, Mail, Lock, User } from 'lucide-react' -import { useRegister } from './register/useRegister' - -export default function RegisterPage(): React.ReactElement { - const { t } = useTranslation() - // Page = wiring container: form state, validation + register flow live in the hook. - const { - username, setUsername, email, setEmail, password, setPassword, - confirmPassword, setConfirmPassword, showPassword, setShowPassword, - isLoading, error, handleSubmit, - } = useRegister() - - return ( -
- {/* Left panel */} -
-
-
- -
-

{t('register.getStarted')}

-

- {t('register.subtitle')} -

- -
- {[ - `✓ ${t('register.feature1')}`, - `✓ ${t('register.feature2')}`, - `✓ ${t('register.feature3')}`, - `✓ ${t('register.feature4')}`, - `✓ ${t('register.feature5')}`, - `✓ ${t('register.feature6')}`, - ].map(item => ( -

{item}

- ))} -
-
-
- - {/* Right panel */} -
-
-
- - TREK -
- -
-

{t('register.createAccount')}

-

{t('register.startPlanning')}

- -
- {error && ( -
- {error} -
- )} - -
- -
- - ) => setUsername(e.target.value)} - required - placeholder="johndoe" - minLength={3} - className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]" - /> -
-
- -
- -
- - ) => setEmail(e.target.value)} - required - placeholder="your@email.com" - className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]" - /> -
-
- -
- -
- - ) => setPassword(e.target.value)} - required - placeholder={t('register.minChars')} - className="w-full pl-10 pr-12 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]" - /> - -
-
- -
- -
- - ) => setConfirmPassword(e.target.value)} - required - placeholder={t('register.repeatPassword')} - className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]" - /> -
-
- - -
- -
-

- {t('register.hasAccount')}{' '} - - {t('register.signIn')} - -

-
-
-
-
-
- ) -} diff --git a/client/src/pages/register/useRegister.ts b/client/src/pages/register/useRegister.ts deleted file mode 100644 index 43cb781f4..000000000 --- a/client/src/pages/register/useRegister.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useAuthStore } from '../../store/authStore' -import { useTranslation } from '../../i18n' - -/** - * Register data hook — owns the form state, the client-side validation and the - * register → redirect flow. RegisterPage is a pure wiring container. Behaviour - * is identical to the previous in-component logic. - */ -export function useRegister() { - const { t } = useTranslation() - const { register } = useAuthStore() - const navigate = useNavigate() - - const [username, setUsername] = useState('') - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [confirmPassword, setConfirmPassword] = useState('') - const [showPassword, setShowPassword] = useState(false) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState('') - - const handleSubmit = async (e: React.FormEvent): Promise => { - e.preventDefault() - setError('') - - if (password !== confirmPassword) { - setError(t('register.passwordMismatch')) - return - } - - if (password.length < 8) { - setError(t('register.passwordTooShort')) - return - } - - setIsLoading(true) - try { - await register(username, email, password) - navigate('/dashboard') - } catch (err: unknown) { - setError(err instanceof Error ? err.message : t('register.failed')) - } finally { - setIsLoading(false) - } - } - - return { - username, setUsername, email, setEmail, password, setPassword, - confirmPassword, setConfirmPassword, showPassword, setShowPassword, - isLoading, error, handleSubmit, - } -} diff --git a/client/tests/unit/mobile/settings/MSettingsMap.test.tsx b/client/tests/unit/mobile/settings/MSettingsMap.test.tsx index 2abbcb9af..b183128fb 100644 --- a/client/tests/unit/mobile/settings/MSettingsMap.test.tsx +++ b/client/tests/unit/mobile/settings/MSettingsMap.test.tsx @@ -108,7 +108,7 @@ describe('MSettingsMap', () => { expect(screen.getByText('Mapbox Standard')).toBeInTheDocument(); expect(screen.getByRole('switch', { name: '3D Buildings & Terrain' })).toBeInTheDocument(); expect(screen.getByRole('switch', { name: 'High Quality Mode' })).toBeInTheDocument(); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-provider', 'mapbox-gl'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-provider', 'mapbox-gl'); expect(screen.queryByTestId('leaflet-preview')).not.toBeInTheDocument(); }); @@ -121,7 +121,7 @@ describe('MSettingsMap', () => { expect(screen.queryByPlaceholderText('pk.eyJ1Ijoi...')).not.toBeInTheDocument(); expect(screen.queryByRole('switch', { name: '3D Buildings & Terrain' })).not.toBeInTheDocument(); expect(screen.getByText('OpenFreeMap Liberty')).toBeInTheDocument(); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-style', 'https://tiles.openfreemap.org/styles/liberty'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-style', 'https://tiles.openfreemap.org/styles/liberty'); expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-3d', 'false'); }); @@ -145,7 +145,7 @@ describe('MSettingsMap', () => { expect(screen.getByText('Preset or OpenFreeMap style URL. OpenFreeMap styles work without a token.')).toBeInTheDocument(); }); - it('FE-MOB-SETMAP-010: a stored mapbox provider hydrates token, style and both switches', () => { + it('FE-MOB-SETMAP-010: a stored mapbox provider hydrates token, style and both switches', async () => { seedMap({ map_provider: 'mapbox-gl', mapbox_access_token: 'pk.token', @@ -158,7 +158,7 @@ describe('MSettingsMap', () => { expect(screen.getByDisplayValue('pk.token')).toBeInTheDocument(); expect(screen.getByRole('switch', { name: '3D Buildings & Terrain' })).toHaveAttribute('aria-checked', 'false'); expect(screen.getByRole('switch', { name: 'High Quality Mode' })).toHaveAttribute('aria-checked', 'true'); - const preview = screen.getByTestId('gl-preview'); + const preview = await screen.findByTestId('gl-preview'); expect(preview).toHaveAttribute('data-token', 'pk.token'); expect(preview).toHaveAttribute('data-3d', 'false'); expect(preview).toHaveAttribute('data-quality', 'true'); @@ -172,7 +172,7 @@ describe('MSettingsMap', () => { await user.click(screen.getByRole('switch', { name: '3D Buildings & Terrain' })); await user.click(screen.getByRole('switch', { name: 'High Quality Mode' })); - const preview = screen.getByTestId('gl-preview'); + const preview = await screen.findByTestId('gl-preview'); expect(preview).toHaveAttribute('data-3d', 'false'); expect(preview).toHaveAttribute('data-quality', 'true'); }); @@ -258,7 +258,7 @@ describe('MSettingsMap', () => { renderMap(); await user.type(screen.getByPlaceholderText('pk.eyJ1Ijoi...'), 'pk.new-token'); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-token', 'pk.new-token'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-token', 'pk.new-token'); await user.click(screen.getByRole('button', { name: 'Save Map' })); expect(updateSettings).toHaveBeenCalledWith( @@ -266,10 +266,10 @@ describe('MSettingsMap', () => { ); }); - it('FE-MOB-SETMAP-019: a mapbox style stored while MapLibre is active is not carried over', () => { + it('FE-MOB-SETMAP-019: a mapbox style stored while MapLibre is active is not carried over', async () => { seedMap({ map_provider: 'maplibre-gl', maplibre_style: 'mapbox://styles/mapbox/dark-v11' }); renderMap(); - expect(screen.getByTestId('gl-preview')).toHaveAttribute('data-style', 'https://tiles.openfreemap.org/styles/liberty'); + expect(await screen.findByTestId('gl-preview')).toHaveAttribute('data-style', 'https://tiles.openfreemap.org/styles/liberty'); }); }); diff --git a/client/vite.config.js b/client/vite.config.js index bc8c067da..79398cd20 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,10 +1,16 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; +import { visualizer } from 'rollup-plugin-visualizer'; -export default defineConfig({ +// `npm run build:analyze` writes dist/stats.html — a treemap of what actually ended +// up in each chunk. The plain build only reports chunk sizes, which tells you a chunk +// is too big but not which dependency made it so. +export default defineConfig(({ mode }) => ({ plugins: [ react(), + mode === 'analyze' && + visualizer({ filename: 'dist/stats.html', gzipSize: true, brotliSize: true }), VitePWA({ registerType: 'autoUpdate', // Serve the generated manifest (+ dev SW) in development too, so the installed @@ -18,8 +24,15 @@ export default defineConfig({ suppressWarnings: true, }, workbox: { - maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, + // Anything above this is dropped from the precache manifest without failing + // the build, so the PWA would just be broken offline. Keep the ceiling close + // to the real bundle so growth shows up as a build warning instead. Revisit + // once route-level splitting lands and the entry chunk shrinks. + maximumFileSizeToCacheInBytes: 6 * 1024 * 1024, globPatterns: ['**/*.{js,css,html,svg,png,woff,woff2,ttf}'], + // build:analyze drops a treemap next to the app; it must never end up in a + // precache manifest if someone ships that build by accident. + globIgnores: ['**/stats.html'], navigateFallback: 'index.html', navigateFallbackDenylist: [ /^\/api/, @@ -127,8 +140,11 @@ export default defineConfig({ ], }, }), - ], + ].filter(Boolean), build: { + // Pin the output level instead of inheriting whatever the current Vite default + // is, so a toolchain bump can't silently change which browsers still work. + target: 'es2022', sourcemap: false, modulePreload: { polyfill: true }, }, @@ -180,4 +196,4 @@ export default defineConfig({ }, }, }, -}); +})); diff --git a/package-lock.json b/package-lock.json index 1c50196d6..2fbeefe8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,8 +29,8 @@ "version": "3.4.1", "dependencies": { "@fontsource/geist-sans": "^5.2.5", + "@fontsource/museomoderno": "^5.3.0", "@fontsource/poppins": "^5.2.7", - "@react-pdf/renderer": "^4.5.1", "@simplewebauthn/browser": "^13.1.2", "@trek/shared": "*", "axios": "^1.6.7", @@ -51,7 +51,6 @@ "react-leaflet-cluster": "^4.1.3", "react-markdown": "^10.1.0", "react-router-dom": "^6.22.2", - "react-window": "^2.2.7", "rehype-sanitize": "^6.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", @@ -71,7 +70,6 @@ "@types/node": "^25.9.3", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", - "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.9", "autoprefixer": "^10.4.18", @@ -87,6 +85,7 @@ "prettier": "^3.8.3", "prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-tailwindcss": "^0.8.0", + "rollup-plugin-visualizer": "^7.0.1", "sharp": "^0.35.0", "tailwindcss": "^3.4.1", "typescript": "^6.0.2", @@ -1858,6 +1857,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2857,6 +2857,15 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource/museomoderno": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/museomoderno/-/museomoderno-5.3.0.tgz", + "integrity": "sha512-DigZe8WH9ohuBcm8WznonufeWb6c21A8Qz59Y2iRQU1UralGrV1zbMiNH+TrE15ftGXqg1HkPEV0Xc7LVvJpIg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource/poppins": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.2.7.tgz", @@ -5424,22 +5433,11 @@ } } }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "devOptional": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -5814,177 +5812,6 @@ "react-dom": "^19.0.0" } }, - "node_modules/@react-pdf/fns": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.3.tgz", - "integrity": "sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==", - "license": "MIT" - }, - "node_modules/@react-pdf/font": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.8.tgz", - "integrity": "sha512-deNd+emtZAJho1IlzKL9bRoLAGv/6oXOIKO2oZfs4RuXUrK1onLHbJO7e2YoVLPFP/sQxisRTnzdJFtd35iKwA==", - "license": "MIT", - "dependencies": { - "@react-pdf/pdfkit": "^5.1.1", - "@react-pdf/types": "^2.11.1", - "fontkit": "^2.0.2", - "is-url": "^1.2.4" - } - }, - "node_modules/@react-pdf/image": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.1.0.tgz", - "integrity": "sha512-ks7Ry8v711r8NvKWSELehj0BXBNPRihSnWsM09nDD8Ur175zbWBCK217LLwQMKDNYDVpkZaipdoJPom1LGaE9g==", - "license": "MIT", - "dependencies": { - "@react-pdf/svg": "^1.1.0", - "jay-peg": "^1.1.1", - "png-js": "^2.0.0" - } - }, - "node_modules/@react-pdf/layout": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.6.1.tgz", - "integrity": "sha512-gN6PmWoEffvlIkifLfEhMsVucRywVMyH3rnxdyOVOhGy0nWJKKGpHyPc4plbDdpP6EfZ0r8prHXujDSkIG2nSA==", - "license": "MIT", - "dependencies": { - "@react-pdf/fns": "3.1.3", - "@react-pdf/image": "^3.1.0", - "@react-pdf/primitives": "^4.3.0", - "@react-pdf/stylesheet": "^6.2.1", - "@react-pdf/textkit": "^6.3.0", - "@react-pdf/types": "^2.11.1", - "emoji-regex-xs": "^1.0.0", - "queue": "^6.0.1", - "yoga-layout": "^3.2.1" - } - }, - "node_modules/@react-pdf/pdfkit": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-5.1.1.tgz", - "integrity": "sha512-wNcdSsNlNYyGHGAgIdt453egBF7fiF9UxpRlklUfVvu8OWCrUppG9xiUrPLVoKiqWet5tMi0w6LmuFUJuYqjEg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.13", - "@noble/ciphers": "^1.0.0", - "@noble/hashes": "^1.6.0", - "browserify-zlib": "^0.2.0", - "fontkit": "^2.0.2", - "jay-peg": "^1.1.1", - "js-md5": "^0.8.3", - "linebreak": "^1.1.0", - "png-js": "^2.0.0", - "vite-compatible-readable-stream": "^3.6.1" - } - }, - "node_modules/@react-pdf/primitives": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.3.0.tgz", - "integrity": "sha512-nYXoZ36pvwNzbc54+DbL8RCn15jU7woJ9D/svnh5tpUXekJ+CbI4mZLo6boSv24CvJgychOu6h7gxX03B4ps0A==", - "license": "MIT" - }, - "node_modules/@react-pdf/reconciler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-2.0.0.tgz", - "integrity": "sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "scheduler": "0.25.0-rc-603e6108-20241029" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@react-pdf/render": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.5.1.tgz", - "integrity": "sha512-IW/N4HWJWtioBXCf7n02IR24VJJ8gbdS3jGypf+vW/rSErEx3/URRzh9UK6Ma8Fpog9+T/W6GE2NHJ5AAKHhVA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.13", - "@react-pdf/fns": "3.1.3", - "@react-pdf/primitives": "^4.3.0", - "@react-pdf/textkit": "^6.3.0", - "@react-pdf/types": "^2.11.1", - "abs-svg-path": "^0.1.1", - "color-string": "^2.1.4", - "normalize-svg-path": "^1.1.0", - "parse-svg-path": "^0.1.2", - "svg-arc-to-cubic-bezier": "^3.2.0" - } - }, - "node_modules/@react-pdf/renderer": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.5.1.tgz", - "integrity": "sha512-5r1VQrE6FRLXX5wWUxwZzM24E2BJMo6g8AQWuS8WyPs9ugu5yMnb2g8/RpPYka/Z6J+RUEWc32wty2NoUJF42Q==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.13", - "@react-pdf/fns": "3.1.3", - "@react-pdf/font": "^4.0.8", - "@react-pdf/layout": "^4.6.1", - "@react-pdf/pdfkit": "^5.1.1", - "@react-pdf/primitives": "^4.3.0", - "@react-pdf/reconciler": "^2.0.0", - "@react-pdf/render": "^4.5.1", - "@react-pdf/types": "^2.11.1", - "events": "^3.3.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "queue": "^6.0.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@react-pdf/stylesheet": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.2.1.tgz", - "integrity": "sha512-2+UEk+7e+z8baaWi2l5kPLWmwtJeOI+T5wW9GGeN3iDH7vd3kbTqOpN1yt9mmfNVZFxQsnDHpznFb5v5UF983A==", - "license": "MIT", - "dependencies": { - "@react-pdf/fns": "3.1.3", - "@react-pdf/types": "^2.11.1", - "color-string": "^2.1.4", - "hsl-to-hex": "^1.0.0", - "media-engine": "^1.0.3", - "postcss-value-parser": "^4.1.0" - } - }, - "node_modules/@react-pdf/svg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@react-pdf/svg/-/svg-1.1.0.tgz", - "integrity": "sha512-cTIHXiz9x1HrbfqzfxfZP3FRdDwUXG77QWF6Fb5MP/lV3ONxR+g0Z3hwtBatCS9HeGBQCpxX/Lzb8wHE+co1PA==", - "license": "MIT", - "dependencies": { - "@react-pdf/primitives": "^4.3.0" - } - }, - "node_modules/@react-pdf/textkit": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.3.0.tgz", - "integrity": "sha512-v6+V8nAcVwm7s2s1jIG2MD3Iw//x/k+XrH1foWOELBE4b32pyDgKyPXN/6KJE0dnX7+fVy27uctLNCLNMvzKzQ==", - "license": "MIT", - "dependencies": { - "@react-pdf/fns": "3.1.3", - "bidi-js": "^1.0.2", - "hyphen": "^1.6.4", - "unicode-properties": "^1.4.1" - } - }, - "node_modules/@react-pdf/types": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.11.1.tgz", - "integrity": "sha512-i9xQgfaDU9QoeNnbp6rltXCWg1huEh195rpOuN8cE4BZ2FuLdQrsIcb2dhFF9aOxXf+XBA6LOSpIW051MDD/bw==", - "license": "MIT", - "dependencies": { - "@react-pdf/font": "^4.0.8", - "@react-pdf/primitives": "^4.3.0", - "@react-pdf/stylesheet": "^6.2.1" - } - }, "node_modules/@remix-run/router": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", @@ -7037,15 +6864,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@swc/types": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", @@ -7588,16 +7406,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/react-window": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", - "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/readdir-glob": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", @@ -8206,12 +8014,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abs-svg-path": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", - "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", - "license": "MIT" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -9077,24 +8879,6 @@ "node": ">=8" } }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.1.2" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "license": "MIT", - "dependencies": { - "pako": "~1.0.5" - } - }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -9174,6 +8958,22 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -9444,15 +9244,6 @@ "node": ">=20" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -9471,27 +9262,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", - "license": "MIT", - "dependencies": { - "color-name": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -10017,6 +9787,36 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -10035,6 +9835,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -10136,12 +9949,6 @@ "wrappy": "1" } }, - "node_modules/dfa": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", - "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", - "license": "MIT" - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -10343,12 +10150,6 @@ "dev": true, "license": "MIT" }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" - }, "node_modules/empathic": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", @@ -10972,15 +10773,6 @@ "node": ">= 0.6" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", @@ -11297,12 +11089,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fflate": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", - "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -11479,23 +11265,6 @@ } } }, - "node_modules/fontkit": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", - "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", - "license": "MIT", - "dependencies": { - "@swc/helpers": "^0.5.12", - "brotli": "^1.3.2", - "clone": "^2.1.2", - "dfa": "^1.2.0", - "fast-deep-equal": "^3.1.3", - "restructure": "^3.0.0", - "tiny-inflate": "^1.0.3", - "unicode-properties": "^1.4.0", - "unicode-trie": "^2.0.0" - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -12131,21 +11900,6 @@ "dev": true, "license": "MIT" }, - "node_modules/hsl-to-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", - "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", - "license": "MIT", - "dependencies": { - "hsl-to-rgb-for-reals": "^1.1.0" - } - }, - "node_modules/hsl-to-rgb-for-reals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", - "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", - "license": "ISC" - }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -12208,12 +11962,6 @@ "node": ">= 6" } }, - "node_modules/hyphen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.14.1.tgz", - "integrity": "sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==", - "license": "ISC" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -12579,6 +12327,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-document.all": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", @@ -12673,6 +12437,38 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -12908,12 +12704,6 @@ ], "license": "MIT" }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT" - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -12960,6 +12750,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -13093,15 +12899,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jay-peg": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", - "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", - "license": "MIT", - "dependencies": { - "restructure": "^3.0.0" - } - }, "node_modules/jimp": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", @@ -13165,12 +12962,6 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "license": "BSD-3-Clause" }, - "node_modules/js-md5": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", - "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13747,25 +13538,6 @@ "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/linebreak": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", - "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", - "license": "MIT", - "dependencies": { - "base64-js": "0.0.8", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/linebreak/node_modules/base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -14370,12 +14142,6 @@ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "license": "CC0-1.0" }, - "node_modules/media-engine": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", - "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", - "license": "MIT" - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -15535,15 +15301,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-svg-path": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", - "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", - "license": "MIT", - "dependencies": { - "svg-arc-to-cubic-bezier": "^3.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15656,6 +15413,27 @@ "wrappy": "1" } }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -15841,12 +15619,6 @@ "dev": true, "license": "MIT" }, - "node_modules/parse-svg-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", - "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", - "license": "MIT" - }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -16126,14 +15898,6 @@ "url-polyfill": "^1.1.13" } }, - "node_modules/png-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/png-js/-/png-js-2.0.0.tgz", - "integrity": "sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==", - "dependencies": { - "fflate": "^0.8.2" - } - }, "node_modules/pngjs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", @@ -16313,6 +16077,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/potpack": { @@ -16321,6 +16086,19 @@ "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", "license": "ISC" }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -16874,15 +16652,6 @@ ], "license": "MIT" }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "license": "MIT", - "dependencies": { - "inherits": "~2.0.3" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -17112,16 +16881,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-window": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz", - "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==", - "license": "MIT", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17472,12 +17231,6 @@ "protocol-buffers-schema": "^3.3.1" } }, - "node_modules/restructure": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", - "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", - "license": "MIT" - }, "node_modules/rettime": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", @@ -17687,6 +17440,60 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-7.0.1.tgz", + "integrity": "sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^11.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^18.0.0" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -17703,6 +17510,19 @@ "node": ">= 18" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -17838,12 +17658,6 @@ "node": ">=v12.22.7" } }, - "node_modules/scheduler": { - "version": "0.25.0-rc-603e6108-20241029", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", - "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", - "license": "MIT" - }, "node_modules/semver": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", @@ -18752,12 +18566,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-arc-to-cubic-bezier": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", - "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", - "license": "ISC" - }, "node_modules/swagger-ui-dist": { "version": "5.32.8", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.8.tgz", @@ -18997,12 +18805,6 @@ "node": ">=0.2.6" } }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -19689,16 +19491,6 @@ "node": ">=4" } }, - "node_modules/unicode-properties": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", - "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", @@ -19709,22 +19501,6 @@ "node": ">=4" } }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/unicode-trie/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "license": "MIT" - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -20156,20 +19932,6 @@ } } }, - "node_modules/vite-compatible-readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", - "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/vite-plugin-pwa": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.3.0.tgz", @@ -21185,6 +20947,23 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -21301,12 +21080,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoga-layout": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", - "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", - "license": "MIT" - }, "node_modules/zip-stream": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz",