From b7f5b0092fac5056c659552766901c760d5b99d2 Mon Sep 17 00:00:00 2001 From: Anton Tranelis Date: Mon, 12 Jan 2026 16:28:10 +0100 Subject: [PATCH] added docs --- docs/PERFORMANCE_OPTIMIZATION_PLAN.md | 409 ++++++++++++++++++++++++++ package-lock.json | 101 +++---- 2 files changed, 448 insertions(+), 62 deletions(-) create mode 100644 docs/PERFORMANCE_OPTIMIZATION_PLAN.md diff --git a/docs/PERFORMANCE_OPTIMIZATION_PLAN.md b/docs/PERFORMANCE_OPTIMIZATION_PLAN.md new file mode 100644 index 00000000..24e2ccba --- /dev/null +++ b/docs/PERFORMANCE_OPTIMIZATION_PLAN.md @@ -0,0 +1,409 @@ +# Performance-Optimierungsplan für Utopia Map + +## Zusammenfassung der Analyse + +Die Performance-Analyse hat drei Hauptbereiche mit Optimierungspotenzial identifiziert: + +1. **React Component Re-Renders** - Unnötige Re-Renders durch Context-Architektur +2. **Map/Leaflet Performance** - Marker-Rendering und Clustering-Konfiguration +3. **Animations/Transitions** - CSS-Optimierungen für flüssigere Übergänge + +--- + +## Kritische Probleme (Höchste Priorität) + +### 1. MarkerClusterGroup Konfiguration +**Datei:** `lib/src/Components/Map/UtopiaMapInner.tsx:329-337` + +```tsx +// PROBLEM: Alle Marker bleiben im DOM, auch wenn off-screen +removeOutsideVisibleBounds={false} +``` + +**Fix:** `removeOutsideVisibleBounds={true}` setzen + +### 2. Marker ohne Memoization +**Datei:** `lib/src/Components/Item/PopupView.tsx:95-183` + +- Alle Marker werden bei jedem State-Change neu gerendert +- Inline Event-Handler erstellen neue Funktionen pro Marker +- `MarkerIconFactory` wird für jeden Marker bei jedem Render aufgerufen + +**Fix:** +- Marker-Komponente mit `React.memo` wrappen +- Event-Handler mit `useCallback` memoizen +- Icon-Erstellung mit `useMemo` cachen + +### 3. Item-Mutation während Render +**Datei:** `lib/src/Components/Item/PopupView.tsx:98-106` + +```tsx +// PROBLEM: Direkte Mutation des Item-Objekts +item.text += '\n\n' +item.tags.map((tag) => { + item.text += `#${encodeTag(tag)}` +}) +``` + +**Fix:** Immutable Kopie erstellen oder Berechnung in useMemo verschieben + +--- + +## Hohe Priorität + +### 4. Context Provider Nesting +**Datei:** `lib/src/Components/AppShell/ContextWrapper.tsx:55-94` + +10 verschachtelte Context-Provider verursachen Kaskaden-Re-Renders. + +**Fix:** +- AppState in separate Contexts aufteilen (UI-State, Theme, Assets) +- `useMemo` für Context-Values verwenden + +### 5. getItemTags O(n) Suche +**Datei:** `lib/src/Components/Map/hooks/useTags.tsx:92-118` + +Lineare Suche für jeden Tag-Match bei jedem Item. + +**Fix:** Map/Set für O(1) Lookups verwenden + +### 6. ProfileForm teure Effect-Berechnung +**Datei:** `lib/src/Components/Profile/ProfileForm.tsx:93-143` + +- Mehrere O(n) Array-Suchen +- Effect läuft bei jeder Items/Tags Änderung + +**Fix:** `useMemo` für berechnete Werte + +--- + +## Mittlere Priorität + +### 7. SetAppState - 4 separate Effects +**Datei:** `lib/src/Components/AppShell/SetAppState.tsx:20-34` + +4 separate Effects statt einem gebatchten Update. + +**Fix:** Zu einem Effect zusammenfassen + +### 8. Fehlende React.memo +- `HeaderView` - `lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx` +- `RelationCard` - `lib/src/Components/Profile/Subcomponents/RelationCard.tsx` +- `ItemViewPopup` - `lib/src/Components/Map/Subcomponents/ItemViewPopup.tsx` +- `FlexView` Template-Komponenten - `lib/src/Components/Profile/Templates/FlexView.tsx` + +### 9. GalleryView ohne useMemo +**Datei:** `lib/src/Components/Profile/Subcomponents/GalleryView.tsx:22-39` + +Images-Array wird bei jedem Render neu berechnet. + +--- + +## Animation Optimierungen + +### 10. Zu lange Transition-Zeiten +**Datei:** `app/src/App.css:18-21` + +```css +/* PROBLEM: 1000ms ist zu langsam */ +.transition-fade { + transition: opacity 1000ms ease; +} +``` + +**Fix:** Auf 300-400ms reduzieren + +### 11. Margin statt Transform für Sidebar +**Dateien:** +- `lib/src/Components/AppShell/SideBar.tsx` +- `lib/src/Components/AppShell/Content.tsx` + +```tsx +// PROBLEM: Margin-Transitions verursachen Layout-Reflows +tw:ml-48 tw:transition-all + +// BESSER: GPU-beschleunigte Transforms +tw:translate-x-48 tw:transition-transform +``` + +### 12. Modal Animation Konflikte +**Datei:** `lib/src/Components/Gaming/Modal.tsx:23-24` + +`tw:transition-none` auf modal-box überschreibt Container-Transition. + +### 13. DialogModal DOM-Manipulation +**Datei:** `lib/src/Components/Templates/DialogModal.tsx` + +Direkte style/classList Manipulation verursacht Layout-Thrashing. + +--- + +## Empfohlene Implementierungsreihenfolge + +### Phase 1: Quick Wins (Sofort umsetzbar) +1. `removeOutsideVisibleBounds={true}` setzen +2. App.css Transition von 1000ms auf 300ms reduzieren +3. SetAppState Effects zusammenfassen + +### Phase 2: Memoization (Mittlerer Aufwand) +4. Marker-Komponente mit React.memo wrappen +5. MarkerIconFactory Ergebnisse cachen +6. HeaderView, RelationCard, ItemViewPopup mit React.memo +7. GalleryView images mit useMemo + +### Phase 3: Architektur (Höherer Aufwand) +8. getItemTags auf Map-basierte Lookups umstellen +9. ProfileForm Berechnungen optimieren +10. Item-Mutation in PopupView beheben + +### Phase 4: CSS/Animation +11. Sidebar Margin → Transform Migration +12. Modal Animation Konflikte beheben +13. will-change Hints hinzufügen + +--- + +## Erwartete Verbesserungen + +- **30-50% weniger Re-Renders** durch Memoization +- **Flüssigere Map-Interaktion** durch Marker-Optimierungen +- **Schnellere Übergänge** durch kürzere Transition-Zeiten +- **Weniger Layout-Thrashing** durch Transform statt Margin + +--- + +## Kritische Dateien + +| Datei | Änderungen | +|-------|------------| +| `UtopiaMapInner.tsx` | Cluster-Config, Event-Handler | +| `PopupView.tsx` | Marker-Memoization, Item-Mutation | +| `useTags.tsx` | Map-basierte Lookups | +| `ProfileForm.tsx` | useMemo für Berechnungen | +| `SetAppState.tsx` | Effects zusammenfassen | +| `App.css` | Transition-Zeiten | +| `SideBar.tsx` / `Content.tsx` | Transform statt Margin | + +--- + +## Messstrategie & Erfolgsmetriken + +### Baseline-Messung (vor Optimierungen) + +#### 1. React DevTools Profiler +```bash +# Chrome Extension: React Developer Tools +# Profiler Tab → Record → Interaktionen durchführen → Stop +``` + +**Zu messende Szenarien:** +- [ ] Map laden mit 100+ Markern → Render-Zeit notieren +- [ ] Filter-Tag hinzufügen/entfernen → Re-Render-Count +- [ ] Sidebar öffnen/schließen → Render-Zeit +- [ ] Item-Popup öffnen → Zeit bis vollständig gerendert +- [ ] Zwischen Items navigieren → Transition-Smoothness + +**Metriken:** +| Szenario | Baseline | Nach Phase 1 | Nach Phase 2 | Ziel | +|----------|----------|--------------|--------------|------| +| Initial Map Render | ___ ms | ___ ms | ___ ms | <500ms | +| Marker Re-Renders | ___ count | ___ count | ___ count | <10 | +| Tag Filter Toggle | ___ ms | ___ ms | ___ ms | <100ms | +| Sidebar Toggle | ___ ms | ___ ms | ___ ms | <50ms | +| Popup Open | ___ ms | ___ ms | ___ ms | <200ms | + +#### 2. Chrome DevTools Performance Panel +``` +F12 → Performance Tab → Record → Interaktionen → Stop +``` + +**Zu messen:** +- **FPS während Animationen** (Ziel: konstant 60 FPS) +- **Long Tasks** (>50ms) identifizieren +- **Layout Shifts** bei Sidebar/Modal +- **Scripting vs Rendering Zeit** + +#### 3. Lighthouse Performance Score +```bash +# Chrome DevTools → Lighthouse → Performance +``` + +**Metriken:** +| Metrik | Baseline | Ziel | +|--------|----------|------| +| Performance Score | ___ | >90 | +| First Contentful Paint | ___ s | <1.5s | +| Largest Contentful Paint | ___ s | <2.5s | +| Total Blocking Time | ___ ms | <200ms | +| Cumulative Layout Shift | ___ | <0.1 | + +#### 4. Custom Performance Markers (Optional) +```tsx +// Kann in kritischen Komponenten eingefügt werden: +useEffect(() => { + performance.mark('PopupView-render-start') + return () => { + performance.mark('PopupView-render-end') + performance.measure('PopupView-render', + 'PopupView-render-start', + 'PopupView-render-end') + console.log(performance.getEntriesByName('PopupView-render')) + } +}, []) +``` + +### Testszenarien für Vergleich + +#### Szenario A: Große Datenmenge +1. Map mit 200+ Items laden +2. 3 Filter-Tags aktivieren +3. Zwischen 5 Items navigieren +4. Sidebar 3x öffnen/schließen + +#### Szenario B: Schnelle Interaktionen +1. Rapid Tag-Toggle (5x schnell hintereinander) +2. Item-Popup öffnen → schließen → nächstes öffnen +3. Sidebar während Map-Pan öffnen + +#### Szenario C: Animation Smoothness +1. Sidebar-Animation beobachten (ruckelt?) +2. Modal öffnen/schließen +3. Zwischen Profile-Views wechseln + +### Erfolgs-Kriterien + +| Phase | Erfolgskriterium | +|-------|------------------| +| Phase 1 (Quick Wins) | Lighthouse Score +10 Punkte, keine sichtbaren Ruckler bei Sidebar | +| Phase 2 (Memoization) | Re-Render Count -50%, Marker-Interaktion <100ms | +| Phase 3 (Architektur) | Initial Load <500ms bei 200 Items | +| Phase 4 (CSS) | Konstant 60 FPS bei allen Animationen | + +### Automatisierte Performance-Tests mit Vitest + +#### Setup + +Neue Datei: `lib/src/__tests__/performance.bench.ts` + +```typescript +import { describe, bench, expect } from 'vitest' +import { render } from '@testing-library/react' +import { performance } from 'perf_hooks' + +// Utility für Performance-Messung +const measureRender = async (Component: React.FC, props: any) => { + const start = performance.now() + const { unmount } = render() + const end = performance.now() + unmount() + return end - start +} + +describe('Performance Benchmarks', () => { + + // Marker Rendering + bench('render 100 markers', async () => { + const items = generateMockItems(100) + await measureRender(PopupView, { items }) + }, { time: 1000 }) + + bench('render 500 markers', async () => { + const items = generateMockItems(500) + await measureRender(PopupView, { items }) + }, { time: 2000 }) + + // Tag Filter Performance + bench('filter toggle with 100 items', async () => { + // Simuliert Tag-Filter Toggle + }) + + // Sidebar Animation + bench('sidebar open/close cycle', async () => { + // Misst Sidebar-Toggle + }) + + // ProfileForm Rendering + bench('ProfileForm with complex item', async () => { + const item = generateComplexItem() + await measureRender(ProfileForm, { item }) + }) +}) +``` + +#### Vitest Config Erweiterung + +```typescript +// vitest.config.ts - benchmark mode hinzufügen +export default defineConfig({ + test: { + benchmark: { + include: ['**/*.bench.ts'], + reporters: ['default', 'json'], + outputFile: './benchmark-results.json', + }, + }, +}) +``` + +#### NPM Script + +```json +// package.json +{ + "scripts": { + "test:perf": "vitest bench", + "test:perf:baseline": "vitest bench --outputFile=baseline.json", + "test:perf:compare": "vitest bench --compare=baseline.json" + } +} +``` + +#### Performance-Grenzwerte + +```typescript +// lib/src/__tests__/performance.thresholds.ts +export const PERF_THRESHOLDS = { + MARKER_RENDER_100: 500, // ms + MARKER_RENDER_500: 2000, // ms + TAG_FILTER_TOGGLE: 100, // ms + SIDEBAR_TOGGLE: 50, // ms + POPUP_OPEN: 200, // ms + PROFILE_RENDER: 300, // ms +} + +// In Tests verwenden: +expect(renderTime).toBeLessThan(PERF_THRESHOLDS.MARKER_RENDER_100) +``` + +#### CI Integration (Optional) + +```yaml +# .github/workflows/performance.yml +name: Performance Benchmarks + +on: + pull_request: + branches: [main] + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm ci + - run: npm run test:perf + - name: Compare with baseline + run: npm run test:perf:compare +``` + +### Dokumentation der Messungen + +Vor jeder Phase: +1. Baseline-Werte in Tabelle eintragen +2. Screenshots von DevTools Profiler speichern +3. Lighthouse Report als PDF exportieren + +Nach jeder Phase: +1. Neue Werte eintragen +2. Vergleich dokumentieren +3. Bei Regression: Ursache analysieren diff --git a/package-lock.json b/package-lock.json index cb2e1317..15e7c5a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -367,7 +367,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1954,9 +1953,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", + "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1973,7 +1972,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.14.0", + "qs": "~6.14.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", @@ -2631,7 +2630,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3511,7 +3509,8 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.20.0", @@ -3761,7 +3760,6 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -4143,12 +4141,6 @@ "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" - }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -4230,7 +4222,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.13.0.tgz", "integrity": "sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -4478,7 +4469,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.13.0.tgz", "integrity": "sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -4624,7 +4614,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.13.0.tgz", "integrity": "sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -4639,7 +4628,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.13.0.tgz", "integrity": "sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -4754,7 +4742,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4762,7 +4751,6 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4881,14 +4869,14 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@types/leaflet": { "version": "1.9.21", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", "license": "MIT", - "peer": true, "dependencies": { "@types/geojson": "*" } @@ -4968,7 +4956,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -4979,7 +4966,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5566,7 +5552,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5785,6 +5770,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -6204,7 +6190,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7243,7 +7228,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-serializer": { "version": "1.4.1", @@ -7422,7 +7408,6 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -7702,7 +7687,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7763,7 +7747,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7841,6 +7824,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "^3.2.7" }, @@ -7860,6 +7844,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -7871,6 +7856,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7906,6 +7892,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -7917,6 +7904,7 @@ "dev": true, "license": "Apache-2.0", "optional": true, + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -7931,6 +7919,7 @@ "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -10243,8 +10232,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/leaflet.locatecontrol": { "version": "0.79.0", @@ -10800,6 +10788,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -10846,7 +10835,6 @@ "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.15.0.tgz", "integrity": "sha512-pPeu/t4yPDX/+Uf9ibLUdmaKbNMlGxMAX+tBednYukol2qNk2TZXAlhdohWxjVvTO3is8crrUYv3Ok02oAaKzA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -11847,6 +11835,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -12240,7 +12229,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12876,7 +12864,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12919,6 +12906,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12934,6 +12922,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -13100,7 +13089,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz", "integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -13130,7 +13118,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -13179,7 +13166,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.3.tgz", "integrity": "sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -13235,9 +13221,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -13290,7 +13276,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -13313,7 +13298,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -13397,14 +13381,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-leaflet": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", "license": "Hippocratic-2.1", - "peer": true, "dependencies": { "@react-leaflet/core": "^2.1.0" }, @@ -13522,9 +13506,9 @@ "license": "0BSD" }, "node_modules/react-router": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", - "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -13544,13 +13528,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", - "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", "license": "MIT", - "peer": true, "dependencies": { - "react-router": "7.11.0" + "react-router": "7.12.0" }, "engines": { "node": ">=20.0.0" @@ -13843,7 +13826,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -14663,6 +14645,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -15191,6 +15174,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -15205,6 +15189,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "minimist": "^1.2.0" }, @@ -15335,7 +15320,6 @@ "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@gerrit0/mini-shiki": "^3.17.0", "lunr": "^2.3.9", @@ -15409,7 +15393,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15478,7 +15461,6 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -16045,7 +16027,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16199,7 +16180,6 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -16681,7 +16661,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -16819,7 +16798,6 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -17101,7 +17079,6 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }