added docs

This commit is contained in:
Anton Tranelis 2026-01-12 16:28:10 +01:00
parent 122091423b
commit b7f5b0092f
2 changed files with 448 additions and 62 deletions

View File

@ -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(<Component {...props} />)
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

101
package-lock.json generated
View File

@ -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"
}