diff --git a/.github/agents/ALIGNMENT_COMPLETE.md b/.github/agents/ALIGNMENT_COMPLETE.md index bdee9b5..5d700cd 100644 --- a/.github/agents/ALIGNMENT_COMPLETE.md +++ b/.github/agents/ALIGNMENT_COMPLETE.md @@ -4,17 +4,17 @@ Final alignment tasks that ensure every agent contract, runbook, and onboarding doc stays in sync before a major phase or hand-off. ## Do -- Review `docs/AGENT_TODO.md`, `docs/TEST_SCRIPT_COORDINATOR.md`, and `docs/AGENTS_INDEX.md` to confirm the current state of gates, artifacts, and command registries. +- Review `hardware/firmware/docs/AGENT_TODO.md`, `docs/TEST_SCRIPT_COORDINATOR.md`, and `docs/AGENTS_INDEX.md` to confirm the current state of gates, artifacts, and command registries. - Ensure every folder-specific `AGENTS*.md` entry (global, firmware, tools, docs, etc.) matches the latest instructions in `.github/agents` briefs; note mismatches in the release log and update the relevant doc. - Check that onboarding materials (`docs/QUICKSTART.md`, `docs/_generated/COCKPIT_COMMANDS.md`) reflect the expected workflows referenced by the new gate or automation plan. -- Record each alignment review, gate status and artifact path in `GIT_WRITE_OPS_FINAL_REPORT.md` or `docs/AGENT_TODO.md` for traceability. +- Record each alignment review, gate status and artifact path in `GIT_WRITE_OPS_FINAL_REPORT.md` or `hardware/firmware/docs/AGENT_TODO.md` for traceability. ## Must Not - Deliver feature changes in this pass; the goal is coherence and evidence before launch. - Skip the safety checkpoint or artifact-tracking mandate from `AGENTS.md`. ## References -- `docs/AGENT_TODO.md` +- `hardware/firmware/docs/AGENT_TODO.md` - `docs/TEST_SCRIPT_COORDINATOR.md` - `docs/AGENTS_INDEX.md` - `.github/agents/*.md` @@ -24,7 +24,6 @@ Final alignment tasks that ensure every agent contract, runbook, and onboarding - run: python3 tools/dev/gen_cockpit_docs.py - run: git status -sb 2. Confirmer la cohérence des AGENT contracts et docs. - - run: rg -n 'AGENT' docs/AGENT_TODO.md + - run: rg -n 'AGENT' hardware/firmware/docs/AGENT_TODO.md 3. Capturer les artefacts/étapes dans les rapports. - run: cat GIT_WRITE_OPS_FINAL_REPORT.md - diff --git a/.github/agents/COPILOT_INDEX.md b/.github/agents/COPILOT_INDEX.md index f5295a1..3f18b5f 100644 --- a/.github/agents/COPILOT_INDEX.md +++ b/.github/agents/COPILOT_INDEX.md @@ -13,11 +13,17 @@ Every `.github/agents/*.md` file is a focused briefing for Copilot/GUIs; chaque | `docs.md` | Docs/onboarding : updates concises, liens valides, structure préservée. | | `kit.md` | GM kit station/export, lien jeu/printables, pas de renommage sans synchro. | | `ci.md` | Workflows/templates : edits minimaux, CI impact reporté. | -| `firmware_core.md` | Firmware tree complet : PlatformIO builds, `docs/AGENT_TODO.md`, logs/artifacts hors git. | +| `firmware_core.md` | Firmware tree complet : PlatformIO builds, `hardware/firmware/docs/AGENT_TODO.md`, logs/artifacts hors git. | | `firmware_tooling.md` | `tools/dev/` helpers : `--help`, grep-friendly logs, ports/timeouts configurables. | | `firmware_copilot.md` | Copilot-specific firmware duties (UI Link, LittleFS, I2S, artefact tracking, sprint/runbook refs). | | `firmware_tests.md` | Smoke/stress scripts + artifact metadata (`meta.json`, `commands.txt`, `summary.md`) et reporting. | | `firmware_docs.md` | Firmware docs/onboarding updates, command index regen, runbook mentions. | +| `ai_voice.md` | ESP-SR wake word + commandes vocales françaises, pipeline I2S → WakeNet → MultiNet → mascarade. | +| `ai_tts.md` | TTS serveur (Coqui XTTS-v2 / Piper), clonage voix Professeur Zacus, streaming audio ESP32. | +| `ai_vision.md` | Détection objets on-device (ESP-DL / ESPDet-Pico), comptage joueurs, reconnaissance props. | +| `ai_hints.md` | Indices adaptatifs LLM, personnalité NPC Professeur Zacus, anti-triche, analytics. | +| `ai_audio_gen.md` | Musique ambiante générative (AudioCraft MusicGen), SFX, soundscapes par salle. | +| `ai_mcp.md` | Serveur MCP hardware : bridge LLM → ESP32 via JSON-RPC 2.0, discovery, auth. | | `ALIGNMENT_COMPLETE.md` | Checklist de pré-lancement : AGENT contracts, runbooks, onboarding, artefacts. | | `PHASE_LAUNCH_PLAN.md` | Checkpoint de lancement de phase : gates, artefacts, reporting RC/AGENT_TODO. | diff --git a/.github/agents/PHASE_LAUNCH_PLAN.md b/.github/agents/PHASE_LAUNCH_PLAN.md index 6b0dd03..d75b1d1 100644 --- a/.github/agents/PHASE_LAUNCH_PLAN.md +++ b/.github/agents/PHASE_LAUNCH_PLAN.md @@ -6,9 +6,9 @@ Execution plan for gating, artifacts, and reporting required to open a new phase ## Do - Define the gate list early: include the PlatformIO matrix, smoke/stress scripts, scenario/audio/printables validators, and any additional QC scripts noted in `docs/TEST_SCRIPT_COORDINATOR.md`. - Capture every artifact/log path under `artifacts/` and `hardware/firmware/logs/`, writing their metadata (`meta.json`, `commands.txt`, `summary.md`) before closing the phase. -- Mention UI Link, WebSocket, HTTP, and I2S health verdicts in `docs/AGENT_TODO.md` and `docs/TEST_SCRIPT_COORDINATOR.md` along with the `artifacts/` references. +- Mention UI Link, WebSocket, HTTP, and I2S health verdicts in `hardware/firmware/docs/AGENT_TODO.md` and `docs/TEST_SCRIPT_COORDINATOR.md` along with the `artifacts/` references. - Summarize the phase status in `docs/RC_FINAL_REPORT_TEMPLATE.md` (if applicable) and note any blockers in `docs/RC_AUTOFIX_CICD.md`. -- Capture the gate/artifact summary and verification commands in `GIT_WRITE_OPS_FINAL_REPORT.md` or `docs/AGENT_TODO.md` so the phase status is documented. +- Capture the gate/artifact summary and verification commands in `GIT_WRITE_OPS_FINAL_REPORT.md` or `hardware/firmware/docs/AGENT_TODO.md` so the phase status is documented. ## Must Not - Leave gates undocumented or skip the regression checks listed in the root `AGENTS.md` or `hardware/firmware/AGENTS.md`. @@ -16,7 +16,7 @@ Execution plan for gating, artifacts, and reporting required to open a new phase ## References - `docs/TEST_SCRIPT_COORDINATOR.md` -- `docs/AGENT_TODO.md` +- `hardware/firmware/docs/AGENT_TODO.md` - `docs/RC_FINAL_REPORT_TEMPLATE.md` - `docs/RC_AUTOFIX_CICD.md` @@ -28,4 +28,3 @@ Execution plan for gating, artifacts, and reporting required to open a new phase 2. Documenter artefacts/logs + verdicts réseau. - run: python3 tools/test/audit_coherence.py - run: cat GIT_WRITE_OPS_FINAL_REPORT.md - diff --git a/.github/agents/ai_audio_gen.md b/.github/agents/ai_audio_gen.md new file mode 100644 index 0000000..a86c8e1 --- /dev/null +++ b/.github/agents/ai_audio_gen.md @@ -0,0 +1,40 @@ +# Custom Agent – AI Generative Audio + +## Scope +Dynamic ambient music generation, SFX creation, and per-room soundscapes driven by game state. + +## Technologies +- AudioCraft MusicGen (Meta), Stable Audio Open +- KXKM-AI RTX 4090 for GPU inference +- PCM/Opus streaming to ESP32 audio system + +## Do +- Build a prompt library mapping room/puzzle states to audio mood descriptors. +- Implement generation pipeline: prompt → MusicGen → normalize → cache → stream. +- Cache generated clips by prompt hash to avoid redundant GPU work. +- Expose REST API: `POST /audio/generate` (prompt → clip), `GET /audio/stream/:id`. +- Integrate with game state engine for automatic soundscape transitions. + +## Must Not +- Generate clips longer than 60 s per request (GPU memory guard). +- Commit generated audio to git; use object storage or Docker volumes. + +## Dependencies +- KXKM-AI RTX 4090 — GPU inference for MusicGen / Stable Audio. +- ESP32 audio system — I2S DAC output, buffer management, volume control. + +## Test Gates +- Generation time < 10 s for a 30 s clip on RTX 4090. +- Audio quality MOS > 3.5 (mean opinion score on 20-sample listening test). + +## References +- AudioCraft: https://github.com/facebookresearch/audiocraft +- Stable Audio Open: https://github.com/Stability-AI/stable-audio-tools + +## Plan d'action +1. Démarrer le service de génération audio sur KXKM-AI. + - run: ssh kxkm@kxkm-ai 'cd /data/zacus-audio && docker compose up -d musicgen' +2. Mesurer le temps de génération sur un clip de 30 s. + - run: python3 tools/ai/audio_gen_bench.py --duration 30 --max-time 10 +3. Vérifier le cache et le streaming vers l'ESP32. + - run: curl -s http://kxkm-ai:5600/audio/generate -d '{"prompt":"mysterious lab ambient","duration":30}' diff --git a/.github/agents/ai_hints.md b/.github/agents/ai_hints.md new file mode 100644 index 0000000..fbf367b --- /dev/null +++ b/.github/agents/ai_hints.md @@ -0,0 +1,41 @@ +# Custom Agent – AI Adaptive Hints + +## Scope +Dynamic hint generation, difficulty adaptation, and NPC Professor Zacus personality via LLM. + +## Technologies +- mascarade API (LLM orchestration layer) +- LLM backends: Qwen2.5-Coder (local), Claude (cloud fallback) +- Conversation memory (Graphiti / context window) + +## Do +- Design prompt templates for Professor Zacus NPC persona (curious, cryptic, encouraging). +- Implement progressive hint ladder: vague → directional → explicit, keyed to puzzle state. +- Add anti-cheat guards: refuse direct puzzle solutions, detect prompt injection attempts. +- Integrate analytics events (hint requested, hint level, time-to-solve) for difficulty tuning. +- Validate hint quality via human evaluation rubric. + +## Must Not +- Leak full puzzle solutions in any hint tier. +- Bypass mascarade API auth or rate limits. +- Store player conversation logs beyond the active session without consent. + +## Dependencies +- mascarade API — LLM routing, model selection, conversation memory. +- Analytics engine — event ingestion for hint/difficulty metrics. + +## Test Gates +- Hint relevance > 90% (human eval on 50-sample test set). +- Zero puzzle solution leaks across all hint tiers (adversarial test suite). + +## References +- mascarade API: `/Users/electron/mascarade` +- `game/prompts/` — prompt template sources + +## Plan d'action +1. Valider les templates de prompts contre le scénario actif. + - run: python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml +2. Lancer la suite de tests anti-triche. + - run: python3 tools/ai/hint_adversarial_test.py --no-leak-tolerance +3. Évaluer la pertinence des indices sur le jeu de test. + - run: python3 tools/ai/hint_relevance_eval.py --samples 50 --threshold 0.90 diff --git a/.github/agents/ai_mcp.md b/.github/agents/ai_mcp.md new file mode 100644 index 0000000..5d34ad7 --- /dev/null +++ b/.github/agents/ai_mcp.md @@ -0,0 +1,41 @@ +# Custom Agent – AI MCP Hardware Server + +## Scope +MCP server bridging LLM tool calls to ESP32 hardware actions (lights, motors, sensors, locks). + +## Technologies +- MCP protocol (Model Context Protocol), JSON-RPC 2.0 +- mascarade MCP client infrastructure +- ESP32 web API (HTTP + WebSocket) + +## Do +- Define MCP tool schemas for each hardware action (e.g., `set_light`, `read_sensor`, `unlock_door`). +- Implement JSON-RPC 2.0 transport (stdio + HTTP/SSE for remote). +- Add device discovery via mDNS or static registry. +- Enforce auth tokens between mascarade and MCP server. +- Return structured results with status codes and sensor readings. + +## Must Not +- Expose hardware tools without authentication (token or mutual TLS). +- Allow unbounded concurrent tool calls to the same device (serialize per-device). +- Commit auth tokens or secrets to git. + +## Dependencies +- mascarade MCP infrastructure — client registration, tool routing. +- ESP32 web API — HTTP endpoints on each device for hardware control. + +## Test Gates +- Tool call round-trip latency < 500 ms (mascarade → MCP server → ESP32 → response). +- 100% success rate on all defined tools against a live or mock device. + +## References +- MCP specification: https://modelcontextprotocol.io +- mascarade MCP client: `/Users/electron/mascarade/core/mcp/` + +## Plan d'action +1. Valider le schéma des outils MCP. + - run: python3 tools/ai/mcp_schema_validate.py --schema mcp/tools.json +2. Tester la latence aller-retour sur un device mock. + - run: python3 tools/ai/mcp_latency_bench.py --target mock --max-latency 500 +3. Vérifier l'authentification et la découverte des devices. + - run: python3 tools/ai/mcp_auth_test.py --require-token diff --git a/.github/agents/ai_tts.md b/.github/agents/ai_tts.md new file mode 100644 index 0000000..cfce7e9 --- /dev/null +++ b/.github/agents/ai_tts.md @@ -0,0 +1,40 @@ +# Custom Agent – AI TTS / Voice Cloning + +## Scope +Server-side text-to-speech, Professor Zacus voice cloning, and audio streaming to ESP32 devices. + +## Technologies +- Coqui XTTS-v2 (voice cloning), Piper TTS (fast fallback) +- Docker deployment on mascarade stack +- PCM/Opus streaming over HTTP chunked transfer + +## Do +- Prepare and curate voice samples for Professor Zacus persona (≥ 30 s clean audio). +- Create Docker Compose service (`zacus-tts`) integrated with mascarade stack. +- Expose REST API: `POST /tts/generate` (text → audio), `POST /tts/stream` (chunked). +- Implement audio format conversion (WAV → PCM 16-bit 16 kHz) for ESP32 I2S playback. +- Cache frequently used phrases to reduce GPU load. + +## Must Not +- Store voice samples in git; keep them in object storage or Docker volumes. +- Bypass mascarade auth on the TTS API endpoints. + +## Dependencies +- mascarade Docker stack — networking, auth, service registry. +- ESP32 audio system — I2S DAC output and buffer management. + +## Test Gates +- Latency < 2 s for a 10-word sentence (first token to last byte). +- Voice similarity > 80% (speaker verification cosine similarity). + +## References +- Coqui XTTS-v2: https://github.com/coqui-ai/TTS +- Piper TTS: https://github.com/rhasspy/piper + +## Plan d'action +1. Construire et démarrer le service TTS Docker. + - run: docker compose -f docker-compose.ai.yml up -d zacus-tts +2. Vérifier la latence de génération. + - run: curl -w '%{time_total}' -X POST http://localhost:5500/tts/generate -d '{"text":"Bonjour explorateurs"}' +3. Valider la similarité vocale sur les échantillons de référence. + - run: python3 tools/ai/tts_similarity_bench.py --threshold 0.80 diff --git a/.github/agents/ai_vision.md b/.github/agents/ai_vision.md new file mode 100644 index 0000000..5171707 --- /dev/null +++ b/.github/agents/ai_vision.md @@ -0,0 +1,40 @@ +# Custom Agent – AI Vision + +## Scope +On-device object detection, player counting, and puzzle prop recognition on ESP32 camera modules. + +## Technologies +- ESP-DL v3.2, ESPDet-Pico (lightweight detector) +- ESP-WHO (face/person detection framework) +- KXKM-AI (RTX 4090) for model training and quantization + +## Do +- Train custom ESPDet-Pico model on puzzle prop dataset (cards, tokens, keys). +- Integrate ESP32-CAM capture pipeline with detection inference loop. +- Expose detection results via local JSON API for puzzle trigger hooks. +- Quantize models to INT8 for ESP32-S3 deployment (PSRAM-aware). +- Maintain a labeled dataset under `data/vision/` with version tags. + +## Must Not +- Stream raw camera frames off-device unless debugging (bandwidth + privacy). +- Commit model weights to git; store in releases or object storage. + +## Dependencies +- ESP32-CAM hardware — OV2640/OV5640 sensor, PSRAM. +- KXKM-AI node — GPU training and INT8 quantization pipeline. + +## Test Gates +- Detection throughput > 7 FPS on ESP32-S3 with PSRAM. +- Accuracy > 85% mAP on the prop test set. + +## References +- ESP-DL: https://github.com/espressif/esp-dl +- ESP-WHO: https://github.com/espressif/esp-who + +## Plan d'action +1. Lancer l'entraînement sur KXKM-AI. + - run: ssh kxkm@kxkm-ai 'cd /data/zacus-vision && python3 train_espdet.py --epochs 50' +2. Quantiser le modèle en INT8. + - run: python3 tools/ai/quantize_model.py --format esp-dl --precision int8 +3. Valider le FPS et la précision sur le firmware. + - run: python3 tools/dev/vision_bench.py --min-fps 7 --min-map 0.85 diff --git a/.github/agents/ai_voice.md b/.github/agents/ai_voice.md new file mode 100644 index 0000000..b719058 --- /dev/null +++ b/.github/agents/ai_voice.md @@ -0,0 +1,39 @@ +# Custom Agent – AI Voice (ESP-SR) + +## Scope +Wake word detection, speech command recognition, and voice pipeline between ESP32 and server (mascarade bridge). + +## Technologies +- ESP-SR v2.0, WakeNet, MultiNet, AFE (Acoustic Front-End) +- I2S microphone input, mascarade REST API + +## Do +- Configure custom wake word ("Professeur Zacus") via WakeNet training tools. +- Implement French command vocabulary in MultiNet with game-relevant phrases. +- Wire I2S mic input through AFE → WakeNet → MultiNet pipeline on ESP32. +- Bridge recognized commands to mascarade API for LLM processing. +- Maintain detection thresholds and noise-gate parameters in a tuneable config header. + +## Must Not +- Ship raw audio to server unless explicitly requested (privacy + bandwidth). +- Modify firmware I2S driver outside `hardware/firmware/` conventions; coordinate with firmware_core agent. + +## Dependencies +- `firmware_core.md` — ESP32 build chain and I2S driver. +- mascarade API — command relay and LLM orchestration. + +## Test Gates +- Wake word detection rate > 95% at 1 m distance, ambient < 50 dB. +- French command accuracy > 90% on the defined vocabulary set. + +## References +- Espressif ESP-SR documentation: https://github.com/espressif/esp-sr +- `hardware/firmware/AGENTS.md` + +## Plan d'action +1. Valider le build ESP-SR avec WakeNet activé. + - run: bash hardware/firmware/build_all.sh +2. Tester la détection du wake word sur le banc de test. + - run: python3 tools/dev/wake_word_bench.py --threshold 0.95 --distance 1m +3. Mesurer la précision des commandes françaises. + - run: python3 tools/dev/command_accuracy.py --lang fr --min-accuracy 0.90 diff --git a/.github/agents/audio.md b/.github/agents/audio.md index 8cdb8c5..e358149 100644 --- a/.github/agents/audio.md +++ b/.github/agents/audio.md @@ -4,8 +4,8 @@ `audio/manifests/**` and derived `audio/generated/**` outputs. ## Do -- Run `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` after edits. -- Refresh exports with `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml` when manifest references change. +- Run `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml` after edits. +- Refresh exports with `python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml` when manifest references change. ## Must Not - Regenerate binary audio assets unless explicitly requested. @@ -16,8 +16,7 @@ ## Plan d’action 1. Valider les manifestes audio et exporter les scénarios. - - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml - - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml + - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml + - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml 2. Relever les IDs utilisés pour éviter les divergences. - - run: rg -n 'id:' audio/manifests/zacus_v1_audio.yaml - + - run: rg -n 'id:' audio/manifests/zacus_v2_audio.yaml diff --git a/.github/agents/firmware_core.md b/.github/agents/firmware_core.md index 19eddae..6e69ed0 100644 --- a/.github/agents/firmware_core.md +++ b/.github/agents/firmware_core.md @@ -6,7 +6,7 @@ All files under `hardware/firmware/**`. ## Do - Follow `hardware/firmware/AGENTS.md`: keep build/smoke gates in `tools/dev`, track structural changes in docs, and update `hardware/firmware/docs/AGENT_TODO.md` with build/smoke state. - Run PlatformIO builds via `./build_all.sh` or the individual matrix before major changes. -- Document UI Link, LittleFS, and I2S status in `docs/AGENT_TODO.md` along with artifact guidance. +- Document UI Link, LittleFS, and I2S status in `hardware/firmware/docs/AGENT_TODO.md` along with artifact guidance. ## Must Not - Commit logs or artifacts; keep them under `hardware/firmware/logs/` or `artifacts/` and mention their paths in reports. @@ -22,4 +22,3 @@ All files under `hardware/firmware/**`. 2. Noter les statuts UI Link/LittleFS/I2S et loggers. - run: bash hardware/firmware/tools/dev/run_smoke_tests.sh - run: python3 hardware/firmware/tools/dev/serial_smoke.py --role auto --wait-port 3 --allow-no-hardware - diff --git a/.github/agents/firmware_tooling.md b/.github/agents/firmware_tooling.md index d355826..81759a5 100644 --- a/.github/agents/firmware_tooling.md +++ b/.github/agents/firmware_tooling.md @@ -9,7 +9,7 @@ - Keep scripts non-interactive when possible and surface a short, grep-friendly summary. ## Must Not -- Skip recording logs/commands in `hardware/firmware/logs/` or the runbook (`docs/AGENT_TODO.md`). +- Skip recording logs/commands in `hardware/firmware/logs/` or the runbook (`hardware/firmware/docs/AGENT_TODO.md`). - Hardcode static port/device names that would break on other machines. ## References diff --git a/.github/agents/game.md b/.github/agents/game.md index d641b06..c15b10b 100644 --- a/.github/agents/game.md +++ b/.github/agents/game.md @@ -5,7 +5,7 @@ ## Do - Treat `game/scenarios/*.yaml` as the single source of truth for story points and content. -- Run `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml` and `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml` after edits. +- Run `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml` and `python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml` after edits. - Re-run audio/printable manifest validators when scenario IDs or references change. ## Must Not @@ -17,9 +17,9 @@ ## Plan d’action 1. Valider le scénario source et régénérer les docs. - - run: python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml - - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml + - run: python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml + - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml + - run: python3 tools/scenario/verify_runtime3_pivots.py game/scenarios/zacus_v2.yaml 2. Revalider les manifestes audio et printables après toute mise à jour. - - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml - - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml - + - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml + - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml diff --git a/.github/agents/global.md b/.github/agents/global.md index 5beb18c..97eff16 100644 --- a/.github/agents/global.md +++ b/.github/agents/global.md @@ -12,8 +12,8 @@ Entire repository; project manager + tech lead + QA gatekeeper as per `AGENTS.md - Touch licensing text or use destructive git commands without explicit requests. ## Gates -- PlatformIO matrix (`pio run -e esp32dev`, `esp32_release`, `esp8266_oled`, `ui_rp2040_ili9488`, `ui_rp2040_ili9486`). -- Scenario/audio/printables validators from `docs/AGENTS_INDEX.md`. +- PlatformIO release gates (`pio run -d hardware/firmware -e freenove_esp32s3`, `pio run -d hardware/firmware -e esp8266_oled`). +- Scenario/audio/printables validators plus Runtime 3 checks from `Makefile` and `tools/test/run_content_checks.sh`. ## References - `AGENTS.md` @@ -24,13 +24,11 @@ Entire repository; project manager + tech lead + QA gatekeeper as per `AGENTS.md - run: git status -sb - run: git diff --stat 2. Exécuter la matrice PlatformIO complète via PlatformIO. - - run: pio run -e esp32dev - - run: pio run -e esp32_release - - run: pio run -e esp8266_oled - - run: pio run -e ui_rp2040_ili9488 - - run: pio run -e ui_rp2040_ili9486 + - run: pio run -d hardware/firmware -e freenove_esp32s3 + - run: pio run -d hardware/firmware -e esp8266_oled 3. Valider les scénarios et manifestes croisés. - - run: python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml - - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml - - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml - - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml + - run: python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml + - run: python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml + - run: python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml + - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml + - run: python3 tools/scenario/verify_runtime3_pivots.py game/scenarios/zacus_v2.yaml diff --git a/.github/agents/kit.md b/.github/agents/kit.md index a044dd0..c7dff71 100644 --- a/.github/agents/kit.md +++ b/.github/agents/kit.md @@ -5,7 +5,7 @@ ## Do - Validate stations via `rg --files kit-maitre-du-jeu` and `rg -n "station|indice|enigme|zacus" kit-maitre-du-jeu`. -- Run `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` when updates touch exported assets. +- Run `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml` when updates touch exported assets. ## Must Not - Rename station identifiers without syncing references across game and printables. @@ -19,5 +19,4 @@ - run: rg --files kit-maitre-du-jeu - run: rg -n 'station|indice|enigme|zacus' kit-maitre-du-jeu 2. Valider tout asset imprimable référencé. - - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml - + - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml diff --git a/.github/agents/printables.md b/.github/agents/printables.md index 8231dd9..813b424 100644 --- a/.github/agents/printables.md +++ b/.github/agents/printables.md @@ -4,7 +4,7 @@ `printables/manifests/**` and `printables/src/**`. ## Do -- Validate `printables/manifests/zacus_v1_printables.yaml` via `python3 tools/printables/validate_manifest.py` after updates. +- Validate `printables/manifests/zacus_v2_printables.yaml` via `python3 tools/printables/validate_manifest.py` after updates. - Preserve deterministic naming and export references for every asset. ## Must Not @@ -16,7 +16,6 @@ ## Plan d’action 1. Valider le manifeste printables. - - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml + - run: python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml 2. Refaire les exports synchronisés si nécessaire (RG). - - run: rg -n 'file:' printables/manifests/zacus_v1_printables.yaml - + - run: rg -n 'file:' printables/manifests/zacus_v2_printables.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 5023995..b9d9aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Changelog ## [Unreleased] + +### Ajouté +- Runtime 3 : compilateur (`tools/scenario/compile_runtime3.py`) et simulateur (`tools/scenario/simulate_runtime3.py`) pour le moteur de scénarios V2. +- Studio visuel React + Blockly dans `frontend-scratch-v2/` pour l'édition graphique des scénarios. +- Schéma Scenario V2 : refonte du format YAML, validation renforcée et pivot vers `game/scenarios/zacus_v2.yaml` comme source canonique. +- Analyse d'intégration IA : `docs/AI_INTEGRATION_ANALYSIS.md` — specs, opportunités et roadmap. +- Documentation : cartes d'architecture (system-map, component-map, data-flow-map, feature-map, migration-map, agent-matrix, release-map), specs de déploiement, runbook opérationnel. +- Spécifications de durcissement sécurité. +- Cibles Makefile : `runtime3-compile`, `runtime3-simulate`, `runtime3-verify`, `runtime3-test`, `runtime3-firmware-bundle`. + +### Modifié +- Consolidation du dépôt : suppression des doublons (`AGENTS 2.md`, `AGENT_TODO 2.md`). +- Sous-module `ESP32_ZACUS` mis à jour (23 commits). + +### Précédemment non versionné - Workflow : nouvelle exportation `tools/scenario/export_md.py` et briefs Markdown (kit + `docs/_generated/SCENARIO_BRIEF.md`) alignés sur `game/scenarios/zacus_v2.yaml`. - Printables : manifeste `printables/manifests/zacus_v2_printables.yaml`, prompts dédiés pour chaque asset et `tools/printables/validate_manifest.py` pour éviter les trous entre IDs et fichiers. - Documentation : AGENTS, WORKFLOWS, GLOSSARY, Quickstart, index et le Makefile rappellent que le YAML est la single source of truth et listent les commandes standard de validation/export. diff --git a/ESP32_ZACUS b/ESP32_ZACUS index db70838..24aad99 160000 --- a/ESP32_ZACUS +++ b/ESP32_ZACUS @@ -1 +1 @@ -Subproject commit db7083845b904d8e5a79cc0f47708feb2685e9d3 +Subproject commit 24aad99533e122df946b3eb007f11b950058ea3e diff --git a/Makefile b/Makefile index f660fb3..de9afff 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,17 @@ PYTHON ?= python3 +SCENARIO ?= game/scenarios/zacus_v2.yaml +FRONTEND_DIR ?= frontend-scratch-v2 -.PHONY: scenario-validate audio-validate printables-validate export all-validate images +.PHONY: bootstrap-validators bootstrap-docs scenario-validate audio-validate printables-validate export validate-runtime-bundle content-checks runtime3-compile runtime3-simulate runtime3-verify runtime3-test runtime3-firmware-bundle frontend-lint frontend-test frontend-build docs-build docs-serve all-validate images + +bootstrap-validators: + bash tools/setup/install_validators.sh + +bootstrap-docs: + $(PYTHON) -m pip install --user --break-system-packages -r tools/requirements/docs.txt scenario-validate: - $(PYTHON) tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml + $(PYTHON) tools/scenario/validate_scenario.py $(SCENARIO) audio-validate: $(PYTHON) tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml @@ -12,9 +20,45 @@ printables-validate: $(PYTHON) tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml export: - $(PYTHON) tools/scenario/export_md.py game/scenarios/zacus_v2.yaml + $(PYTHON) tools/scenario/export_md.py $(SCENARIO) -all-validate: scenario-validate audio-validate printables-validate +validate-runtime-bundle: + $(PYTHON) tools/scenario/validate_runtime_bundle.py + +content-checks: + bash tools/test/run_content_checks.sh + +runtime3-compile: + $(PYTHON) tools/scenario/compile_runtime3.py $(SCENARIO) + +runtime3-simulate: + $(PYTHON) tools/scenario/simulate_runtime3.py $(SCENARIO) + +runtime3-verify: + $(PYTHON) tools/scenario/verify_runtime3_pivots.py $(SCENARIO) + +runtime3-test: + $(PYTHON) -m unittest discover -s tests/runtime3 -p 'test_*.py' + +runtime3-firmware-bundle: + $(PYTHON) tools/scenario/export_runtime3_firmware_bundle.py $(SCENARIO) + +frontend-lint: + cd $(FRONTEND_DIR) && npm run lint + +frontend-test: + cd $(FRONTEND_DIR) && npm run test + +frontend-build: + cd $(FRONTEND_DIR) && npm run build + +docs-build: + $(PYTHON) -m mkdocs build --strict + +docs-serve: + $(PYTHON) -m mkdocs serve + +all-validate: scenario-validate audio-validate printables-validate validate-runtime-bundle runtime3-compile runtime3-simulate runtime3-verify runtime3-test @echo "All validations passed." images: diff --git a/README.md b/README.md index c91c435..a3b2f64 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,100 @@ +# Le Mystere du Professeur Zacus -# 🤖🎩 Le Mystère du Professeur Zacus 🎩🤖 +Zacus est en refonte vers un produit hybride unique: +- un jeu terrain fiable sur carte Freenove ESP32-S3, +- un studio auteur moderne en React + Blockly, +- un runtime portable "Zacus Runtime 3" compile depuis le YAML canonique. -![Couverture](./assets/cover.png) +## Canon actuel +- Source narrative: `game/scenarios/zacus_v2.yaml` +- Studio auteur: `frontend-scratch-v2/` +- Runtime portable: `tools/scenario/compile_runtime3.py` + `tools/scenario/simulate_runtime3.py` +- Cible hardware principale: `hardware/firmware` avec `freenove_esp32s3` +- Plans et memoire: `memory/`, `plans/`, `todos/` +- Architecture et cartes Mermaid: `docs/architecture/` -> **Bienvenue dans l’enquête la plus instable du multivers** : indices imprimables, audio, modules électroniques intégrés, et un guide MJ qui ne bug jamais (sauf si tu l’imprimes en 3D). -> -> **⚛ L’électron rare — ⚡ unstable by design** -> -> **Auteur : Clément SAILLANT** +## Démarrage rapide -[![Build](https://img.shields.io/badge/build-validate%20%2B%20export-brightgreen)](./.github/workflows/validate.yml) -[![PlatformIO](https://img.shields.io/badge/PlatformIO-ready-brightgreen)](https://platformio.org/) -[![Code: MIT](https://img.shields.io/badge/code-MIT-blue)](./LICENSE) -[![Content: CC%20BY--NC%204.0](https://img.shields.io/badge/content-CC%20BY--NC%204.0-orange)](./LICENSE-CONTENT.md) - - -## ⚡ Pitch (30 secondes chrono) - -Le Professeur Zacus a disparu. Son labo est sous tension : **signaux audio**, **capsules d’indices**, **preuves imprimées**… et un dispositif électronique indispensable qui réagit à chaque étape de l’enquête. - -Les joueurs fouillent, recoupent, déduisent — comme une vraie équipe d’enquête, mais avec plus de blagues et moins de panique. -Le MJ déroule une session fluide, avec des checkpoints et une fin satisfaisante (sauf si tu oublies le gâteau). - -> *"Si tu trouves une LED qui clignote, c’est normal. Si elle te parle, c’est probablement un bug... ou le MJ qui s’ennuie."* ---- - -## ✅ Ce que tu obtiens (concret, pas du vent) - -- **Printables** prêts à imprimer (indices, cartes, accessoires) -- **Guide Maître du Jeu** (mise en place, script, solutions) -- **Audio** (timers / ambiance / déclenchements) -- **Scénario YAML** = source de vérité (durée/difficulté modulables) -- **Option électronique** : ESP32/Arduino (UI, effets, interactions) - -> Tout est pensé pour être **rejouable** et **facile à préparer**. Même pour les MJ qui n’ont jamais touché un oscilloscope. -> *"MJ : Maître du Jeu, mais aussi Maître du Jazz, Maître du Jenga, Maître du Juste Prix... à toi de choisir."* - ---- - -## 🕹️ Pour qui / durée / niveau de fun - -- **Joueurs** : 6–14 (recommandé), ou équipes de 2–4 -- **Durée** : 105 min (45 + 60, modulable selon le groupe) -- **Âge** : famille / anniversaire (adaptable, sauf pour les robots) -- **Matériel** : imprimante + modules électroniques (ESP32 + écran / interface tactile) pour piloter les phases de jeu - -> *"Le mode ‘slow motion’ est réservé aux anniversaires avec trop de bonbons."* - ---- - -## 🎬 Démo - -![Démo](./assets/demo.gif) - -> *"Si tu croises un QR code dans les archives, scanne-le. Si tu croises un QR code sur ton gâteau, scanne-le aussi (on ne sait jamais)."* ---- - -## 🧠 Comment ça marche (en 1 minute, ou 42 secondes si tu es pressé) - -### Source de vérité : le scénario YAML -Le scénario principal est dans `game/scenarios/`. Il pilote : -- les étapes / stations, -- les validations / codes, -- les exports (briefs MJ, docs, manifestes). - -### Pipeline du repo -`game/scenarios/*.yaml -> tools/ (validate + export) -> kit MJ / printables / audio -> hardware/firmware/` - -![Diagramme](./assets/diagram.png) - ---- - -## 🧩 Démarrage rapide (MJ, version turbo) - -> *"Si tu veux une expérience vraiment futuriste, branche un ESP32 sur ton chat. (Non, ne fais pas ça, mais l’idée est marrante.)"* - -1. Lis le **guide MJ** : `kit-maitre-du-jeu/` -2. Imprime les **printables** : `printables/` -3. Prépare l’audio : `audio/` -4. Prépare, câble et flashe l’électronique : `hardware/` (un kit esp32-S3 avec écran est requis pour chaque partie) -5. Lance la partie 🎩 - -👉 FAQ / dépannage : `docs/faq.md` (aucune question idiote, sauf “où est le MJ ?”) - ---- - -## 🛠️ Démarrage rapide (dev, version quantum) - -> *"Astuce dev : Si tu valides le YAML sans erreur du premier coup, tu gagnes un badge ‘Zacus Quantum’ (à imprimer toi-même)."* - -Installer les validateurs : +### 1. Bootstrap validation ```bash bash tools/setup/install_validators.sh +bash tools/test/run_content_checks.sh ``` -Valider le scénario officiel : +### 2. Compiler et simuler Runtime 3 ```bash -python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml +python3 tools/scenario/compile_runtime3.py game/scenarios/zacus_v2.yaml +python3 tools/scenario/simulate_runtime3.py game/scenarios/zacus_v2.yaml +python3 tools/scenario/export_runtime3_firmware_bundle.py game/scenarios/zacus_v2.yaml ``` -Exporter un brief Markdown : -```bash -python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml -o docs/exports/zacus_v2.md -``` - -Frontend canon (V2) : +### 3. Démarrer le studio React + Blockly ```bash cd frontend-scratch-v2 npm install +npm test VITE_STORY_API_BASE=http://:8080 npm run dev ``` ---- - -## 🧾 Contenu du dépôt (repères galactiques) - -> *"Ce dépôt est plus organisé que le bureau du Professeur Zacus (et ça, c’est pas peu dire)."* - -```text -game/scenarios/ Scénarios YAML (source de vérité) -kit-maitre-du-jeu/ Guide MJ + solutions + script -printables/ Cartes/indices + manifestes -audio/ Manifestes audio + ressources -tools/ Validation + export + génération -hardware/ Firmware & accessoires électroniques (prérequis) -docs/ Mini-site / FAQ / ressources +### 4. Utiliser le shell canonique +```bash +./tools/dev/zacus.sh content-checks +./tools/dev/zacus.sh runtime3-compile +make runtime3-verify +make runtime3-test +./tools/dev/zacus.sh frontend-test +./tools/dev/zacus.sh frontend-build +./tools/dev/zacus.sh menu ``` ---- +## Cartographie du dépôt +- `game/`: scénarios YAML canoniques. +- `audio/`: manifestes audio et assets associés. +- `printables/`: manifestes et exports imprimables. +- `kit-maitre-du-jeu/`: matériel MJ et déroulé terrain. +- `frontend-scratch-v2/`: studio auteur React + Blockly. +- `hardware/firmware/`: firmware, APIs device, scripts terrain. +- `tools/`: validateurs, compilateur/simulateur Runtime 3, shells d'automatisation. +- `docs/`: quickstart, architecture, benchmark OSS et runbooks. +- `memory/`, `plans/`, `todos/`: pilotage de la refonte. -## 🧷 Visuels +## AI Integration -> *"Si tu reconnais le prototype sur la photo, tu es officiellement un expert en électronique de fête."* +Le projet intègre une couche IA pour enrichir l'expérience terrain : +- **Voice pipeline** : wake word (ESP-SR) → ASR → LLM → TTS (Piper / XTTS-v2) → speaker. Scaffold prêt, voir [`docs/voice/VOICE_PIPELINE_GUIDE.md`](docs/voice/VOICE_PIPELINE_GUIDE.md). +- **Vision** : détection d'objets via ESP-DL pour indices contextuels. +- **LLM hints** : le Professeur Zacus répond aux joueurs via mascarade. +- **TUI dev** : script interactif d'orchestration → `python3 tools/dev/zacus_tui.py` -![Prototype](./assets/board.png) +Analyse complète : [`docs/AI_INTEGRATION_ANALYSIS.md`](docs/AI_INTEGRATION_ANALYSIS.md) -![Printables](./assets/printables.png) +## Sécurité & Déploiement ---- +- [`docs/SECURITY.md`](docs/SECURITY.md) — audit firmware, HMAC auth, rate limiting +- [`docs/DEPLOYMENT_RUNBOOK.md`](docs/DEPLOYMENT_RUNBOOK.md) — procédures de déploiement terrain -## 🧑‍🎓 Licence +## Statut du projet -- **Code** : MIT (`LICENSE`) -- **Contenu créatif** (scénarios, docs, printables, assets) : CC BY‑NC 4.0 (`LICENSE-CONTENT.md`) +- Runtime 3 : compilateur + simulateur + export firmware bundle OK +- Studio auteur : React 19 + Blockly, 18 tests passing +- Firmware : sécurité P0 intégrée (HMAC, rate limit, safe OTA) +- Voice pipeline : scaffold ESP-SR prêt, TTS Docker validé +- Specs : `ZACUS_RUNTIME_3_SPEC.md`, `STORY_DESIGNER_SCRATCH_LIKE_SPEC.md` +## Documentation à lire +- `docs/QUICKSTART.md` +- `docs/architecture/index.md` +- `docs/AI_INTEGRATION_ANALYSIS.md` +- `specs/ZACUS_RUNTIME_3_SPEC.md` +- `specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md` +- `docs/benchmark-oss.md` -> *"Ce jeu est garanti sans IA malveillante, mais avec des enfants qui peuvent hacker le scénario à tout moment."* ---- +## Notes de refonte +- Le YAML reste la source de vérité pendant la migration. +- Le Runtime 3 devient le contrat portable entre studio, simulateur et firmware. +- `hardware/firmware/esp32/` reste en lecture seule. +- Les chemins legacy ne doivent être supprimés qu'après preuve de remplacement. -## 🤝 Crédits - -> *"Merci à tous les MJ, enfants, parents, et robots qui ont testé ce scénario. Mention spéciale à ceux qui ont trouvé le twist avant la LED rouge."* - -**Auteur : Clément SAILLANT** -Signature : **⚛ L’électron rare** — **⚡ unstable by design** - -> *"Ce projet a été validé par un oscilloscope, un grille-pain, et une IA qui adore les énigmes."* - ---- - -OpenGraph : `assets/og.png` (1200×630) -- generation/story-ia : scénarios IA +## Licences +- Code: MIT (`LICENSE`) +- Contenu créatif: CC BY-NC 4.0 (`LICENSE-CONTENT.md`) diff --git a/docs/AGENTS_DOCS.md b/docs/AGENTS_DOCS.md index 27ad071..ff5206c 100644 --- a/docs/AGENTS_DOCS.md +++ b/docs/AGENTS_DOCS.md @@ -4,7 +4,7 @@ Docs agents govern `docs/`, `README.md`, `esp32_audio/README.md`, and any onboarding / briefing files (`.github/agents/`, `docs/_generated/`). ## Doit -- Avant toute mise à jour, lire `docs/AGENT_TODO.md` pour ne pas dupliquer les efforts de coordination. +- Avant toute mise à jour, lire `hardware/firmware/docs/AGENT_TODO.md` pour ne pas dupliquer les efforts de coordination. - Refléter tout changement d’organisation ou de procédure dans `docs/TEST_SCRIPT_COORDINATOR.md`, `docs/TEST_COHERENCE_AUDIT_RUNBOOK.md`, et les AGENTS concernés. - Maintenir `docs/_generated/COCKPIT_COMMANDS.md` en cohérence avec `tools/dev/cockpit_commands.yaml` via `python3 tools/dev/gen_cockpit_docs.py`. - Documenter les nouveaux artefacts (gates/tests) dans la section adéquate du runbook et mentionner les chemins dans le reporting template. @@ -12,7 +12,7 @@ Docs agents govern `docs/`, `README.md`, `esp32_audio/README.md`, and any onboar ## Artefacts - Les docs n’intègrent pas d’artefacts binaires ; citez plutôt les dossiers `artifacts/` lorsque vous décrivez un protocole ou un gate réussi. -- Pour les instructions “TODO”, mettez à jour `docs/AGENT_TODO.md` (bloc 4/5) en parallèle des docs. +- Pour les instructions “TODO”, mettez à jour `hardware/firmware/docs/AGENT_TODO.md` (bloc 4/5) en parallèle des docs. ## Bonnes pratiques -- Chaque mise à jour de runbook ou guide doit inclure un rappel sur la nécessité de respecter `docs/AGENT_TODO.md` comme tracker unique. +- Chaque mise à jour de runbook ou guide doit inclure un rappel sur la nécessité de respecter `hardware/firmware/docs/AGENT_TODO.md` comme tracker unique. diff --git a/docs/AGENTS_FIRMWARE.md b/docs/AGENTS_FIRMWARE.md index 623660e..162a542 100644 --- a/docs/AGENTS_FIRMWARE.md +++ b/docs/AGENTS_FIRMWARE.md @@ -4,15 +4,15 @@ Firmware agents cover `esp32_audio/`, `ui/esp8266_oled/`, `ui/rp2040_tft/`, and shared `protocol/` code that drives the MCU binaries. ## Doit -- Lire puis mettre à jour `docs/AGENT_TODO.md` avant d’agir (le tracker unique garde la trace de l’état de chaque gate). +- Lire puis mettre à jour `hardware/firmware/docs/AGENT_TODO.md` avant d’agir (le tracker unique garde la trace de l’état de chaque gate). - Respecter le contrat global (`AGENTS.md`) et les règles d’outillage de `tools/dev/AGENTS.md` (gates/scripts centralisés, artefacts hors git). -- S’assurer que tout build se passe via `platformio.ini`/`build_all.sh` ou `Makefile fast-*`, et documenter les artefacts dans `docs/AGENT_TODO.md`. -- Pour les modifications de structure, ajouter un commentaire dans `docs/AGENT_TODO.md` + mentionner le commit dans `docs/TEST_SCRIPT_COORDINATOR.md`. -- Toujours signaler l’état des UI Link / LittleFS / I2S dans `docs/AGENT_TODO.md:6-14` lorsqu’on manipule ces composants. +- S’assurer que tout build se passe via `platformio.ini`/`build_all.sh` ou `Makefile fast-*`, et documenter les artefacts dans `hardware/firmware/docs/AGENT_TODO.md`. +- Pour les modifications de structure, ajouter un commentaire dans `hardware/firmware/docs/AGENT_TODO.md` + mentionner le commit dans `docs/TEST_SCRIPT_COORDINATOR.md`. +- Toujours signaler l’état des UI Link / LittleFS / I2S dans `hardware/firmware/docs/AGENT_TODO.md:6-14` lorsqu’on manipule ces composants. ## Reporting - Chaque run doit produire des artefacts `artifacts//` ; les chemins doivent figurer dans la case correspondante du TODO. -- Données hardware (logs, ports) restent hors git : référencer les fichiers dans `docs/AGENT_TODO.md` ou le rapport final. +- Données hardware (logs, ports) restent hors git : référencer les fichiers dans `hardware/firmware/docs/AGENT_TODO.md` ou le rapport final. ## References - Branche principale : `docs/SPRINT_RECOMMENDATIONS.md`, `README.md`, `protocol/ui_link_v2.md` diff --git a/docs/AGENTS_TESTS.md b/docs/AGENTS_TESTS.md index 0ea2ab6..2f3d48b 100644 --- a/docs/AGENTS_TESTS.md +++ b/docs/AGENTS_TESTS.md @@ -4,15 +4,15 @@ Tests agents focus on files under `tools/dev/`, `tools/test/`, `esp32_audio/tests/`, and the artifacts/log directories (`artifacts/`, `logs/`). ## Doit -- Toujours lire/mettre à jour `docs/AGENT_TODO.md` pour annoncer les gates en cours ou bloqués (ex. smoke, stress, audit). +- Toujours lire/mettre à jour `hardware/firmware/docs/AGENT_TODO.md` pour annoncer les gates en cours ou bloqués (ex. smoke, stress, audit). - Exécuter les calibrations/gates via les scripts officiels (`tools/dev/run_matrix_and_smoke.sh`, `tools/dev/run_smoke_tests.sh`, `tools/dev/run_stress_tests.py`, `tools/test/audit_coherence.py`), puis consigner les artefacts sous `artifacts/`. - Tenir `docs/TEST_SCRIPT_COORDINATOR.md` à jour avec les résultats et les chemins d’évidence (section “Exécutions récentes” + reporting template). - Valider les commandes cockpit via `tools/dev/cockpit_commands.yaml` et `docs/_generated/COCKPIT_COMMANDS.md` (regénération avec `python3 tools/dev/gen_cockpit_docs.py` si besoin). -- Mentionner les échecs réseau (HTTP/WebSocket/WiFi) dans `docs/AGENT_TODO.md` et `docs/TEST_SCRIPT_COORDINATOR.md` pour la coordination. +- Mentionner les échecs réseau (HTTP/WebSocket/WiFi) dans `hardware/firmware/docs/AGENT_TODO.md` et `docs/TEST_SCRIPT_COORDINATOR.md` pour la coordination. ## Artefacts - Les logs exigent `meta.json`, `git.txt`, `commands.txt`, `summary.md` (au minimum) ; vérifiez les répertoires demandés dans `docs/TEST_SCRIPT_COORDINATOR.md`. - Pas de commit d’artefacts ; les chemins doivent être mentionnés dans le TODO. ## Bonnes pratiques -- En cas de panne persistante (UI link, stress panic, scénario manquant), capturez les traces dans `artifacts/rc_live/` et notez la prochaine action dans `docs/AGENT_TODO.md`. +- En cas de panne persistante (UI link, stress panic, scénario manquant), capturez les traces dans `artifacts/rc_live/` et notez la prochaine action dans `hardware/firmware/docs/AGENT_TODO.md`. diff --git a/docs/AI_INTEGRATION_ANALYSIS.md b/docs/AI_INTEGRATION_ANALYSIS.md new file mode 100644 index 0000000..6b3acfa --- /dev/null +++ b/docs/AI_INTEGRATION_ANALYSIS.md @@ -0,0 +1,235 @@ +# Analyse IA & Intégration — Le Mystere du Professeur Zacus + +> Generee le 2026-03-21 par analyse exhaustive (firmware, frontend, tooling, docs, web research) + +--- + +## 1. SWOT — Firmware ESP32-S3 + +### Forces +- Architecture modulaire (audio/UI/network/scenario managers) +- Gestion memoire PSRAM mature (caps_allocator, fallback chains) +- Audio I2S avec protection underrun, DMA async +- LVGL avec DMA flush async, SIMD optionnel +- Runtime 3 step-based avec transitions event-driven + +### Faiblesses (CRITIQUES) +| ID | Severite | Issue | Fichier | +|----|----------|-------|---------| +| FW-01 | CRITICAL | Credentials WiFi en dur | storage_manager.cpp:73 | +| FW-02 | CRITICAL | API web sans authentification | main.cpp:5932-5960 | +| FW-03 | HIGH | Watchdog timeout (calculator eval) | main.cpp + platformio.ini | +| FW-04 | HIGH | Pas de validation input API | main.cpp:5945-5950 | +| FW-05 | HIGH | Pas de rate limiting | main.cpp:5200-5960 | +| FW-06 | HIGH | Pas de timeout JSON parsing | main.cpp | +| FW-07 | MEDIUM | LVGL fragmentation (54KB pool) | platformio.ini:80 | +| FW-08 | MEDIUM | Audio underrun sans recovery | audio_manager.cpp:407-418 | +| FW-09 | MEDIUM | Buffer overflow string ops | ui_manager.cpp:145 | +| FW-10 | MEDIUM | Pas de HTTPS/TLS | main.cpp:5966 | + +### Opportunites +- OTA firmware updates (partition scheme compatible) +- Secure Boot + Flash encryption (ESP32-S3 natif) +- Auth middleware centralise pour webOnApi() +- Watchdog supervisor software + +--- + +## 2. SWOT — Frontend React+Blockly + +### Forces +- Architecture composants clean (4 onglets) +- API client complet (30+ endpoints, dual protocol) +- Blockly bidirectionnel (workspace <-> YAML) +- TypeScript strict + Zod validation +- Accessibilite (aria-label, aria-live) + +### Faiblesses +| ID | Severite | Issue | Fichier | +|----|----------|-------|---------| +| FE-01 | HIGH | Zero tests (0% coverage) | — | +| FE-02 | HIGH | Pas de React ErrorBoundary | App.tsx | +| FE-03 | HIGH | Pas de timeout API requests | api.ts:21-34 | +| FE-04 | MEDIUM | Blockly registration globale mutable | BlocklyDesigner.tsx:35-86 | +| FE-05 | MEDIUM | Pas de reconnexion WebSocket | api.ts:274-289 | +| FE-06 | MEDIUM | Bundle bloat (Blockly+Monaco ~2.5MB) | package.json | +| FE-07 | LOW | Tab state non persiste | App.tsx:22 | +| FE-08 | LOW | Pas de dark mode | App.css | + +--- + +## 3. SWOT — Python Tooling + +### Forces +- Pipeline clair (compile -> simulate -> validate -> export) +- Validation semantique comprehensive +- Simulation deterministe avec detection cycles (max_steps) +- Shell scripts robustes (set -euo pipefail) + +### Faiblesses +| ID | Severite | Issue | Fichier | +|----|----------|-------|---------| +| PY-01 | HIGH | Seulement 5 tests (pas de negatifs) | test_runtime3_routes.py | +| PY-02 | HIGH | Pas de detection cycles transitions | runtime3_common.py:227-233 | +| PY-03 | MEDIUM | Schema version hard-codee (v1 only) | runtime3_common.py:196 | +| PY-04 | MEDIUM | normalize_token() fallback silencieux | runtime3_common.py:31-33 | +| PY-05 | LOW | Pas de TypedDict/dataclass partout | runtime3_common.py | + +--- + +## 4. Documentation — Etat + +| Zone | Completude | Action | +|------|-----------|--------| +| Architecture (8 maps) | 100% | A jour | +| Specifications (13 specs) | 90% | 3 specs critiques manquantes | +| Getting Started | 95% | OK | +| Operations | 30% | Runbook manquant | +| Securite | 10% | Stub seulement | +| Tests/QA | 40% | Pas de matrice unifiee | + +### Specs MANQUANTES +1. `DEPLOYMENT_RUNBOOK.md` — procedures terrain +2. `SECURITY.md` — modele auth, menaces, remediations +3. `MCP_HARDWARE_SERVER_SPEC.md` — integration mascarade MCP +4. `ANALYTICS_OBSERVABILITY_SPEC.md` — telemetrie temps reel +5. `QA_TEST_MATRIX_SPEC.md` — matrice de tests formelle +6. `NETWORK_TOPOLOGY_SPEC.md` — ESP-NOW format messages + +### Fichiers OBSOLETES a supprimer +- `docs/AGENTS 2.md`, `docs/AGENT_TODO 2.md` (duplicates) +- `docs/AGENTS_DOCS.md`, `docs/AGENTS_FIRMWARE.md` (remplace par .github/agents/) +- `docs/GENERER_UN_SCENARIO_STORY_V2.md` (references obsoletes) + +--- + +## 5. Etat de l'Art IA 2026 — Opportunites d'Integration + +### TOP 5 Technologies Prioritaires + +| # | Technologie | Usage Zacus | Maturite | Licence | +|---|------------|-------------|----------|---------| +| 1 | **ESP-SR v2.0** (Espressif) | Wake word "Hey Zacus" + commandes vocales offline (300 mots) | Production | Espressif | +| 2 | **Coqui XTTS-v2** | Cloner la voix du Prof Zacus (6s sample) pour narration dynamique | Production | MPL-2.0 | +| 3 | **ESP-DL v3.2** | Detection objets on-device (YOLOv11n, 7 FPS) pour puzzles physiques | Production | MIT | +| 4 | **ESP RainMaker MCP** | Controle materiel via LLM ("allume la lampe UV salle 3") | Production | Apache 2.0 | +| 5 | **AudioCraft MusicGen** | Musique ambiante generative par salle/puzzle sur KXKM-AI | Production | MIT/CC-BY-NC | + +### Projets de Reference + +| Projet | Stars | Pertinence | URL | +|--------|-------|-----------|-----| +| **XiaoZhi ESP32** | 25k+ | Architecture quasi-identique (ESP32-S3 + wake + LLM + TTS via MCP) | github.com/78/xiaozhi-esp32 | +| **Willow** | — | Pipeline voix ESP32-S3 <500ms latence | github.com/HeyWillow/willow | +| **ClueControl** | — | Puzzles Arduino escape room (RFID, maglocks) | github.com/ClueControl | +| **EscapeRoom (devlinb)** | — | Backend Node.js anti-prompt-injection pour hints IA | github.com/devlinb/escaperoom | +| **IoT-MCP (Duke)** | — | Framework MCP pour IoT, 205ms latence, 74KB RAM | github.com/Duke-CEI-Center/IoT-MCP-Servers | + +### Architecture IA Cible + +```mermaid +flowchart TD + subgraph ESP32-S3["ESP32-S3 (On-Device)"] + SR[ESP-SR v2.0
Wake Word + Commands] + DL[ESP-DL v3.2
Object Detection] + CAM[Camera OV2640] + MIC[Microphone I2S] + SPK[Speaker I2S] + end + + subgraph Server["Serveur mascarade"] + LLM[LLM via mascarade API
Hints adaptatifs] + TTS[Coqui XTTS-v2
Voix Prof Zacus] + MCP[MCP Server
Hardware Control] + ANALYTICS[Analytics Engine
Difficulte adaptative] + end + + subgraph KXKM["KXKM-AI (RTX 4090)"] + MUSIC[AudioCraft MusicGen
Ambient + SFX] + TRAIN[Fine-tune modeles
voix/detection custom] + end + + MIC --> SR + CAM --> DL + SR -->|"commande vocale"| MCP + DL -->|"objet detecte"| MCP + MCP -->|"action puzzle"| ESP32-S3 + MCP <-->|"API mascarade"| LLM + LLM -->|"hint text"| TTS + TTS -->|"audio stream"| SPK + MUSIC -->|"ambient MP3"| SPK + ESP32-S3 -->|"telemetrie"| ANALYTICS + ANALYTICS -->|"ajuster difficulte"| LLM +``` + +--- + +## 6. Plan d'Integration IA — Phases + +### Phase A: Fondations Securite (P0 — 1-2 semaines) +1. Supprimer credentials WiFi en dur → NVS + provisioning QR +2. Ajouter auth Bearer token sur tous les endpoints API +3. Input validation + rate limiting +4. Augmenter LVGL pool 54→96KB +5. Augmenter stack Arduino 16→24KB + +### Phase B: Voice Pipeline (P1 — 2-4 semaines) +1. Integrer ESP-SR v2.0 pour wake word "Hey Zacus" +2. Deployer Coqui XTTS-v2 en Docker sur VM mascarade +3. Pipeline: ESP32 mic → WiFi stream → mascarade → LLM → TTS → ESP32 speaker +4. Commandes vocales offline (MultiNet, 50 mots FR) +5. Ref: XiaoZhi ESP32 architecture + +### Phase C: Vision & Detection (P1 — 2-4 semaines) +1. Integrer ESP-DL v3.2 pour detection objets puzzle +2. Entrainer modele custom (props specifiques Zacus) +3. Face detection pour comptage joueurs (ESP-WHO) +4. Au-dela du QR basique: detection indices physiques + +### Phase D: LLM Hints Adaptatifs (P2 — 4-6 semaines) +1. API mascarade comme backend LLM pour hints contextuels +2. Prompt engineering anti-triche (ref: devlinb/escaperoom) +3. Analytics temps reel → ajustement difficulte +4. Prof Zacus comme NPC LLM avec memoire conversation + +### Phase E: Audio Generatif (P2 — 2-3 semaines) +1. AudioCraft MusicGen sur KXKM-AI (RTX 4090) +2. Generation ambiante par salle/puzzle +3. SFX dynamiques via Stable Audio Open +4. Streaming vers ESP32 speakers + +### Phase F: MCP & Orchestration (P3 — 4-6 semaines) +1. MCP server hardware (ESP RainMaker MCP pattern) +2. Integration mascarade MCP existant +3. Controle naturel-language de tous les peripheriques +4. Dashboard game master temps reel + +--- + +## 7. Corrections Prioritaires Code + +### Immediate (cette semaine) +``` +FW-01: storage_manager.cpp — NVS credentials +FW-02: main.cpp — Bearer auth middleware +FW-03: platformio.ini — stack 16→24KB +FE-02: App.tsx — ErrorBoundary wrapper +FE-03: api.ts — timeout 5s defaut +``` + +### Court terme (2 semaines) +``` +FW-04-06: main.cpp — input validation, rate limit, JSON timeout +FW-07: platformio.ini — LVGL pool 54→96KB +PY-01: tests — 5→25+ tests avec negatifs +PY-02: runtime3_common.py — detection cycles +FE-01: frontend — premiers tests Vitest +``` + +### Moyen terme (1 mois) +``` +FW-08-10: audio recovery, string safety, TLS +FE-04-06: Blockly cleanup, WS reconnect, bundle split +PY-03-05: schema migration, TypedDict, normalize warnings +DOCS: specs manquantes + cleanup obsoletes +``` diff --git a/docs/DEPLOYMENT_RUNBOOK.md b/docs/DEPLOYMENT_RUNBOOK.md new file mode 100644 index 0000000..8154472 --- /dev/null +++ b/docs/DEPLOYMENT_RUNBOOK.md @@ -0,0 +1,446 @@ +# Deployment Runbook + +## Status +- State: draft +- Date: 2026-03-21 +- Audience: Game master, field technician +- Related: `docs/QUICKSTART.md`, `docs/SECURITY.md` + +--- + +## 1) Pre-Deployment Checklist + +### Hardware +- [ ] ESP32-S3 Freenove board powered and accessible via USB +- [ ] Speaker connected to I2S output (verified with test tone) +- [ ] LED strips connected and addressed (WS2812B data pin) +- [ ] Camera OV2640 connected (if vision features enabled) +- [ ] Microphone INMP441 connected (if voice features enabled) +- [ ] All puzzle actuators (servos, relays) wired and tested +- [ ] Backup ESP32 board available on-site + +### Network +- [ ] WiFi access point configured and powered +- [ ] SSID and password documented (not the default) +- [ ] ESP32 IP address reserved (DHCP reservation or static) +- [ ] mascarade server reachable from game WiFi (if AI features) +- [ ] Game master device (laptop/tablet) on same network + +### Content +- [ ] Scenario YAML validated: `bash tools/test/run_content_checks.sh` +- [ ] Runtime 3 IR compiled: `python3 tools/scenario/compile_runtime3.py game/scenarios/zacus_v2.yaml` +- [ ] Runtime 3 simulation passed: `python3 tools/scenario/simulate_runtime3.py game/scenarios/zacus_v2.yaml` +- [ ] Audio files present in `hardware/firmware/data/audio/` +- [ ] All audio files referenced in scenario exist on filesystem +- [ ] Printable materials generated and printed + +### Security +- [ ] API bearer token generated and stored in NVS +- [ ] WiFi credentials stored in NVS (not hardcoded) +- [ ] `.env` file configured on server with matching token +- [ ] Serial debug disabled in production firmware +- [ ] Review `docs/SECURITY.md` checklist + +--- + +## 2) Firmware Flash Procedure + +### 2.1 Prerequisites + +```bash +# Install PlatformIO CLI (if not installed) +pip install platformio + +# Verify toolchain +pio platform update espressif32 +``` + +### 2.2 Build Firmware + +```bash +cd hardware/firmware + +# Build for Freenove ESP32-S3 +pio run -e freenove_esp32s3 + +# Build for ESP8266 OLED (secondary display) +pio run -e esp8266_oled +``` + +### 2.3 Flash Firmware + +```bash +# Connect ESP32-S3 via USB, identify port +ls /dev/tty.usb* # macOS +ls /dev/ttyUSB* # Linux + +# Flash firmware + bootloader + partition table +pio run -e freenove_esp32s3 -t upload --upload-port /dev/tty.usbmodem* + +# Monitor serial output to verify boot +pio device monitor -e freenove_esp32s3 --port /dev/tty.usbmodem* -b 115200 +``` + +### 2.4 Expected Boot Output + +``` +[INFO] Zacus Runtime 3 v3.1 +[INFO] Free heap: 245000 bytes +[INFO] PSRAM: 8388608 bytes +[INFO] WiFi connecting to: EscapeRoom-Net +[INFO] WiFi connected, IP: 192.168.0.50 +[INFO] HTTP server started on port 8080 +[INFO] Scenario loaded: ZACUS_V2 (11 steps) +[INFO] Initial step: RTC_ESP_ETAPE1 +[INFO] Audio manager ready +[INFO] LED manager ready (60 LEDs) +[INFO] Ready. +``` + +### 2.5 Provisioning Credentials + +On first boot (or after NVS erase): + +```bash +# Erase NVS to force setup mode +pio run -e freenove_esp32s3 -t erase + +# Flash and boot — device enters AP mode "Zacus-Setup" +pio run -e freenove_esp32s3 -t upload + +# Connect to "Zacus-Setup" WiFi from your phone/laptop +# Open http://192.168.4.1 in browser +# Enter WiFi SSID, password, and API token +# Device reboots in STA mode +``` + +--- + +## 3) Content Deployment (LittleFS) + +### 3.1 Prepare Filesystem Image + +Content files are stored in `hardware/firmware/data/`: + +``` +data/ + story/ + runtime3/ + DEFAULT.json # Runtime 3 IR (compiled) + scenarios/ + DEFAULT.json # Legacy scenario + audio/ + ambient_01.mp3 + hint_01.mp3 + sfx_unlock.mp3 + ... + config/ + device.json # Device-specific config +``` + +### 3.2 Upload LittleFS Image + +```bash +cd hardware/firmware + +# Build and upload filesystem image +pio run -e freenove_esp32s3 -t uploadfs --upload-port /dev/tty.usbmodem* +``` + +### 3.3 Verify Content Upload + +```bash +# Via serial +> ls /littlefs/story/runtime3/ +DEFAULT.json (4523 bytes) + +# Via API (requires auth) +curl -H "Authorization: Bearer " http://192.168.0.50:8080/api/status +# Should show: "scenario": "ZACUS_V2", "steps": 11 +``` + +### 3.4 Update Content Without Reflashing Firmware + +For content-only updates (new scenario, audio files): + +```bash +# Recompile scenario +python3 tools/scenario/compile_runtime3.py game/scenarios/zacus_v2.yaml + +# Copy to firmware data directory +cp artifacts/runtime3/latest/DEFAULT.json hardware/firmware/data/story/runtime3/ + +# Upload only filesystem (firmware unchanged) +pio run -e freenove_esp32s3 -t uploadfs +``` + +--- + +## 4) Frontend Deployment + +### 4.1 Build Frontend + +```bash +cd frontend-scratch-v2 + +# Install dependencies +npm install + +# Run tests +npm test + +# Run lint +npm run lint + +# Build for production +VITE_STORY_API_BASE=http://192.168.0.50:8080 npm run build +``` + +Build output is in `frontend-scratch-v2/dist/`. + +### 4.2 Serve Frontend + +**Option A: From ESP32 (embedded)** +```bash +# Copy built files to firmware data directory +cp -r frontend-scratch-v2/dist/* hardware/firmware/data/www/ + +# Upload filesystem +cd hardware/firmware +pio run -e freenove_esp32s3 -t uploadfs +``` + +The ESP32 serves the frontend at `http://:8080/`. + +**Option B: From separate server** +```bash +# Serve with any static file server +npx serve frontend-scratch-v2/dist -l 3000 + +# Or via zacus.sh +./tools/dev/zacus.sh frontend-serve +``` + +### 4.3 Using the Shell Shortcut + +```bash +# Build + lint + test in one command +./tools/dev/zacus.sh frontend-build +``` + +--- + +## 5) Network Setup + +### 5.1 WiFi Access Point Configuration + +| Parameter | Recommended Value | +|-----------|------------------| +| SSID | `EscapeRoom-Net` (hidden optional) | +| Security | WPA2-PSK | +| Channel | 1, 6, or 11 (least congested) | +| Band | 2.4 GHz (ESP32 does not support 5 GHz) | +| DHCP range | 192.168.0.50 - 192.168.0.99 | +| DNS | Not required (local network only) | + +### 5.2 IP Address Assignments + +| Device | IP | Port | Purpose | +|--------|-----|------|---------| +| WiFi AP / Router | 192.168.0.1 | — | Gateway | +| ESP32 main | 192.168.0.50 | 8080 | Game device | +| ESP32 salle 2 | 192.168.0.51 | 8080 | Secondary device | +| Game master laptop | 192.168.0.10 | 3000 | Dashboard | +| mascarade server | 192.168.0.119 | 4001 | AI backend | + +### 5.3 ESP-NOW Pairing + +For multi-device setups using ESP-NOW mesh: + +```bash +# On primary device (serial console) +> espnow pair +Pairing mode active. Press button on secondary device... + +# On secondary device (serial console) +> espnow join +Scanning for primary... +Paired with zacus-main (MAC: AA:BB:CC:DD:EE:FF) +``` + +### 5.4 Verify Network Connectivity + +```bash +# Ping ESP32 +ping 192.168.0.50 + +# Check API health +curl http://192.168.0.50:8080/health +# Expected: {"status":"ok"} + +# Check authenticated endpoint +curl -H "Authorization: Bearer " http://192.168.0.50:8080/api/status +# Expected: full status JSON + +# Check mascarade (if AI features) +curl http://192.168.0.119:4001/health +``` + +--- + +## 6) Post-Deployment Validation + +### 6.1 Functional Checks + +| # | Check | Command / Action | Expected | +|---|-------|-----------------|----------| +| 1 | Device boots | Power on, watch serial | "Ready." message | +| 2 | WiFi connects | Serial log | IP address assigned | +| 3 | API responds | `curl /health` | `{"status":"ok"}` | +| 4 | Auth works | `curl /api/status` with token | 200 OK | +| 5 | Auth rejects | `curl /api/status` without token | 401 | +| 6 | Scenario loaded | Check `/api/status` response | Correct scenario ID | +| 7 | Audio plays | Trigger audio via API or button | Sound from speaker | +| 8 | LEDs respond | Trigger LED via API or scenario | Correct colors | +| 9 | Frontend loads | Open browser to device IP | UI renders | +| 10 | Full walkthrough | Play through scenario start to finish | All transitions work | + +### 6.2 Automated Validation Script + +```bash +#!/bin/bash +# tools/deploy/validate.sh +set -euo pipefail + +ESP_IP="${1:-192.168.0.50}" +TOKEN="${2:-$(cat .env | grep API_TOKEN | cut -d= -f2)}" + +echo "Validating deployment at $ESP_IP..." + +# Health check +curl -sf "http://$ESP_IP:8080/health" | jq .status +echo "Health: OK" + +# Auth check +HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer $TOKEN" \ + "http://$ESP_IP:8080/api/status") +[ "$HTTP_CODE" = "200" ] && echo "Auth: OK" || echo "Auth: FAIL ($HTTP_CODE)" + +# Scenario check +SCENARIO=$(curl -sf -H "Authorization: Bearer $TOKEN" \ + "http://$ESP_IP:8080/api/status" | jq -r .scenario) +echo "Scenario: $SCENARIO" + +# Step count +STEPS=$(curl -sf -H "Authorization: Bearer $TOKEN" \ + "http://$ESP_IP:8080/api/status" | jq .steps) +echo "Steps: $STEPS" + +echo "Validation complete." +``` + +--- + +## 7) Rollback Procedure + +### 7.1 Firmware Rollback + +```bash +# Keep previous firmware binary +cp .pio/build/freenove_esp32s3/firmware.bin backups/firmware_$(date +%Y%m%d).bin + +# To rollback: +pio run -e freenove_esp32s3 -t upload --upload-port /dev/tty.usbmodem* \ + --firmware backups/firmware_20260320.bin +``` + +### 7.2 Content Rollback + +```bash +# Content is versioned in git +git log --oneline hardware/firmware/data/story/ + +# Restore previous version +git checkout HEAD~1 -- hardware/firmware/data/story/runtime3/DEFAULT.json + +# Re-upload filesystem +cd hardware/firmware +pio run -e freenove_esp32s3 -t uploadfs +``` + +### 7.3 Frontend Rollback + +```bash +# Previous builds in git +git log --oneline frontend-scratch-v2/ + +# Restore and rebuild +git checkout HEAD~1 -- frontend-scratch-v2/ +cd frontend-scratch-v2 && npm run build +``` + +### 7.4 Emergency: Factory Reset + +```bash +# Full erase (firmware + NVS + filesystem) +pio run -e freenove_esp32s3 -t erase + +# Reflash everything from scratch +pio run -e freenove_esp32s3 -t upload +pio run -e freenove_esp32s3 -t uploadfs + +# Re-provision credentials via AP mode +``` + +--- + +## 8) Troubleshooting Quick Reference + +| Symptom | Likely Cause | Fix | +|---------|-------------|-----| +| No serial output | Wrong USB port or baud rate | Try `115200` baud, check cable (data-capable) | +| Boot loop | Stack overflow or panic | Increase stack in `platformio.ini` (16 -> 24 KB) | +| WiFi won't connect | Wrong credentials or channel | Erase NVS, re-provision. Check 2.4 GHz only | +| WiFi keeps disconnecting | Signal too weak or interference | Move AP closer, change channel | +| API returns 401 | Token mismatch | Re-provision token via AP setup portal | +| API returns 429 | Rate limited | Wait 1 s, reduce request frequency | +| No audio | I2S pin mismatch or volume 0 | Check `platformio.ini` pin defines, set volume > 0 | +| Audio distorted | DMA buffer underrun | Reduce concurrent tasks, increase DMA buffer count | +| LEDs wrong color | GRB/RGB order mismatch | Check LED type in code (WS2812B = GRB) | +| LEDs flicker | Insufficient power supply | Use dedicated 5V supply, add 100uF cap | +| LVGL crash | Pool too small | Increase to 96 KB in `platformio.ini` | +| Scenario stuck | Missing transition for current state | Check `DEFAULT.json` transitions, use serial `> status` | +| ESP-NOW not pairing | Different WiFi channels | Both devices must be on same channel | +| Camera timeout | I2C conflict or power issue | Check camera ribbon cable, power cycle | +| OOM / heap exhaustion | Memory leak or too many features | Monitor with `> heap`, disable unused features | +| Frontend won't connect | Wrong `VITE_STORY_API_BASE` | Rebuild with correct ESP32 IP | +| mascarade unreachable | Network or Docker issue | Check `ping`, `docker ps`, firewall rules | + +### Serial Debug Commands + +``` +> status # Device state, heap, uptime +> heap # Detailed memory breakdown +> wifi # WiFi RSSI, IP, channel +> scenario # Current step, available transitions +> transition # Manually trigger a transition +> audio list # List available audio files +> audio play # Play an audio file +> led test # Cycle through LED test patterns +> reboot # Software restart +> factory-reset # Erase NVS + reboot into AP mode +``` + +### Useful Monitoring Commands + +```bash +# Watch serial output continuously +pio device monitor -e freenove_esp32s3 -b 115200 + +# Stream API status every 5 seconds +watch -n 5 'curl -s -H "Authorization: Bearer " http://192.168.0.50:8080/api/status | jq .' + +# Check WiFi signal from ESP32 perspective +curl -s -H "Authorization: Bearer " http://192.168.0.50:8080/api/status | jq .wifi_rssi +``` diff --git a/docs/FIX_STRUCTURE_20260301.md b/docs/FIX_STRUCTURE_20260301.md deleted file mode 100644 index 6f83a80..0000000 --- a/docs/FIX_STRUCTURE_20260301.md +++ /dev/null @@ -1,87 +0,0 @@ -# Structure & Configuration Fixes (2026-03-01) - -## Summary -Fixed the double `hardware/firmware` directory duplication and locked `cockpit.sh` to use ONLY PlatformIO CLI. - -## Changes Made - -### 1. Directory Structure Correction -**Problem:** Double nesting `hardware/firmware/hardware/firmware/` contained actual sources -**Solution:** -- Moved `esp32_audio/`, `ui_freenove_allinone/`, `ui/` to correct location -- Removed empty `hardware/firmware/hardware/` directory -- New clean structure: `hardware/firmware/{esp32_audio/, ui_freenove_allinone/, ui/, lib/, ...}` - -### 2. platformio.ini Fixes -**Problem:** Paths referenced `hardware/firmware` double, wrong `libs` path -**Changes:** -```ini -# Before: -src_dir = hardware/firmware -build_flags = -Ihardware/firmware/esp32_audio/src -I$PROJECT_DIR/hardware/libs/story - -# After: -src_dir = . -build_flags = -I./esp32_audio/src -I$PROJECT_DIR/lib/story -``` - -**All corrections:** -- `src_dir`: `hardware/firmware` → `.` (current directory only) -- Include paths: Removed all `hardware/firmware` double-references -- Library paths: `libs/` → `lib/` (correct singular form) -- Relative includes: `./lib/story` instead of complex relative paths - -### 3. cockpit.sh Locked to PIO-Only -**Problem:** Complex script with hardware detection, artifact management, build orchestration -**Solution:** Rewritten as pure PlatformIO CLI wrapper - -**Removed:** -- ❌ Hardware auto-detection -- ❌ build_all.sh integration -- ❌ Artifact collection/logging -- ❌ Complex FX/graphics verification -- ❌ 600+ lines of legacy code - -**Added:** -- ✅ Direct pio run / pio run -t upload / pio device monitor -- ✅ Environment listing (envs) and port detection (ports) -- ✅ Locked contract: "ONLY uses PIO (PlatformIO CLI)" -- ✅ Clean 113-line script with help, examples, clear contract - -**New usage:** -```bash -./tools/dev/cockpit.sh build [env] # pio run -e $env -./tools/dev/cockpit.sh flash [env] # pio run -e $env -t upload -./tools/dev/cockpit.sh monitor [env] # pio device monitor -e $env -./tools/dev/cockpit.sh go [env] # Build + Flash + Monitor combo -./tools/dev/cockpit.sh envs # List available environments -./tools/dev/cockpit.sh ports # List connected serial ports -./tools/dev/cockpit.sh help # Show help with contract -``` - -## Files Modified -1. `platformio.ini` - Fixed all path references -2. `tools/dev/cockpit.sh` - Rewrote to locked PIO-only contract - -## Verification -```bash -# Verify directory structure -ls -1d hardware/firmware/{esp32_audio,ui_freenove_allinone,ui,lib} -# Output: All 4 dirs present ✓ - -# Verify cockpit.sh works -./tools/dev/cockpit.sh envs -# Output: Lists 9+ environments ✓ - -# Verify no more hardware/firmware double-refs -grep -c 'hardware/firmware' platformio.ini -# Output: 0 ✓ -``` - -## Notes -- Build may show missing headers (e.g., `boot_protocol_runtime.h`) - this is a separate issue in source files, not related to the path corrections -- cockpit.sh now has a strict locked contract: zero custom logic, zero shell complexity -- All future cockpit.sh changes must use ONLY `pio` commands, no custom detection/scripts - -## Rollback (if needed) -Original files backed up in git; use `git checkout hardware/firmware/platformio.ini tools/dev/cockpit.sh` to revert. diff --git a/docs/GENERER_UN_SCENARIO_STORY_V2.md b/docs/GENERER_UN_SCENARIO_STORY_V2.md index f7fd766..8ea6d98 100644 --- a/docs/GENERER_UN_SCENARIO_STORY_V2.md +++ b/docs/GENERER_UN_SCENARIO_STORY_V2.md @@ -1,91 +1,96 @@ -# Générer un scénario STORY V2 (firmware ESP32) +# Générer un scénario STORY V2 -Ce guide explique **comment créer/modifier un scénario** pour le firmware ESP32 sans toucher au moteur C++. +Ce guide explique **comment créer, compiler et tester un scénario** avec le Runtime 3 sans toucher au moteur C++. ## Prérequis -Depuis ce dossier: +- Python 3.x avec PyYAML installé (`pip install pyyaml`) +- Le dépôt cloné à la racine du projet -```bash -cd hardware/firmware/esp32_audio +## 1) Source canonique du scénario + +Le fichier de référence est : + +``` +game/scenarios/zacus_v2.yaml ``` -## 1) Créer (ou dupliquer) un scénario YAML +C'est la **single source of truth**. Toute modification de scénario doit passer par ce fichier YAML. -Point de départ recommandé: +## 2) Édition visuelle (studio Blockly) -- template: `docs/protocols/story_specs/templates/scenario.template.yaml` -- exemple existant: `docs/protocols/story_specs/scenarios/default_unlock_win_etape2.yaml` -- schéma de référence: `docs/protocols/story_specs/schema/story_spec_v1.yaml` +Pour éditer le scénario graphiquement, utiliser le studio React + Blockly : -Crée un nouveau fichier dans `docs/protocols/story_specs/scenarios/`, par exemple: +```bash +cd frontend-scratch-v2 +npm install +npm run dev +``` -`docs/protocols/story_specs/scenarios/mon_scenario.yaml` - -Exemple prêt à l'emploi dans le repo: - -- `docs/protocols/story_specs/scenarios/example_unlock_express.yaml` -- `docs/protocols/story_specs/scenarios/example_unlock_express_done.yaml` - -## 2) Définir la structure minimale - -Dans le YAML, renseigne au minimum: - -- `id` (identifiant unique) -- `version` (utiliser la version supportée par le générateur; dans ce repo les exemples sont en `2`) -- `initial_step` -- `app_bindings` -- `steps` -- `transitions` - -Règle pratique: -- chaque `target_step_id` doit pointer vers une étape existante, -- les IDs doivent être uniques, -- les transitions doivent permettre d’atteindre une fin logique (`DONE` ou équivalent). +Le studio permet de manipuler les étapes, transitions et événements visuellement, puis d'exporter vers le format YAML V2. ## 3) Valider le scénario ```bash -make story-validate +make scenario-validate ``` -Cette commande vérifie: +Cette commande vérifie : -- la conformité de structure, -- les IDs / références, +- la conformité de structure au schéma V2, +- les IDs / références croisées, - la cohérence des transitions, - les bindings des mini-apps. -Corrige les erreurs jusqu’à obtenir une validation propre. +Corriger les erreurs jusqu'à obtenir une validation propre. -## 4) Générer le code C++ +## 4) Compiler avec le Runtime 3 ```bash -make story-gen +make runtime3-compile ``` -Le générateur produit les fichiers utilisés par le runtime V2: - -- `src/story/generated/scenarios_gen.h` -- `src/story/generated/scenarios_gen.cpp` -- `src/story/generated/apps_gen.h` -- `src/story/generated/apps_gen.cpp` - -## 5) Compiler et flasher +Cela exécute `tools/scenario/compile_runtime3.py` sur le scénario par défaut. Pour spécifier un fichier : ```bash +make runtime3-compile SCENARIO=game/scenarios/zacus_v2.yaml +``` + +## 5) Simuler le scénario + +```bash +make runtime3-simulate +``` + +Cela exécute `tools/scenario/simulate_runtime3.py` et déroule le scénario hors firmware pour vérifier les transitions et les états finaux. + +## 6) Vérifier les pivots et générer le bundle firmware + +```bash +make runtime3-verify +make runtime3-firmware-bundle +``` + +- `runtime3-verify` valide les points pivots du graphe de scénario. +- `runtime3-firmware-bundle` produit le bundle prêt à flasher sur l'ESP32. + +## 7) Lancer les tests unitaires Runtime 3 + +```bash +make runtime3-test +``` + +## 8) Compiler et flasher le firmware + +```bash +cd ESP32_ZACUS pio run -e esp32dev -``` - -Puis upload selon ton port série: - -```bash pio run -e esp32dev -t upload --upload-port /dev/ttyUSB0 ``` -## 6) Tester côté série +## 9) Tester côté série -Dans le moniteur série, commandes utiles: +Dans le moniteur série, commandes utiles : - `STORY_V2_ENABLE ON` - `STORY_V2_LIST` @@ -97,15 +102,27 @@ Dans le moniteur série, commandes utiles: ## Workflow court (copier-coller) ```bash -cd hardware/firmware/esp32_audio -make story-validate -make story-gen -pio run -e esp32dev +make scenario-validate +make runtime3-compile +make runtime3-simulate +make runtime3-verify ``` +## Toutes les cibles Makefile disponibles + +| Cible | Description | +|---|---| +| `scenario-validate` | Validation YAML du scénario | +| `runtime3-compile` | Compilation Runtime 3 | +| `runtime3-simulate` | Simulation hors firmware | +| `runtime3-verify` | Vérification des pivots | +| `runtime3-test` | Tests unitaires Runtime 3 | +| `runtime3-firmware-bundle` | Export bundle firmware ESP32 | + ## Conseils pour éviter les blocages -- Commence simple: 3 à 4 étapes max au début. -- N’ajoute qu’un seul nouveau type d’événement à la fois. -- Teste chaque transition clé au moniteur série (`STORY_V2_EVENT ...`). -- Garde un scénario « de secours » valide dans `docs/protocols/story_specs/scenarios/`. +- Commencer simple : 3 à 4 étapes max au début. +- N'ajouter qu'un seul nouveau type d'événement à la fois. +- Toujours valider (`make scenario-validate`) avant de compiler. +- Tester chaque transition clé au moniteur série (`STORY_V2_EVENT ...`). +- Utiliser `make runtime3-simulate` pour déboguer sans flasher. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 1628da5..8e257c5 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,82 +1,98 @@ # Quickstart -## En un coup d’œil -- Source de vérité : `game/scenarios/zacus_v2.yaml` (dérive tout le reste : kit MJ, audio, printables). -- Public : 6 à 14 enfants, 105 minutes (45 + 60). -- Matériel : `kit-maitre-du-jeu/`, `printables/`, `game/scenarios/zacus_v2.yaml`, `audio/manifests/zacus_v2_audio.yaml`, `hardware/firmware/` (ESP32 + écran tactile). -- Licences : contenus créatifs CC BY-NC 4.0 (`LICENSES/CC-BY-NC-4.0.txt`), code/script MIT (`LICENSES/MIT.txt`). - -## Installer -1. Copie les imprimables depuis `printables/export/pdf/` ou génère-les via `printables/manifests/zacus_v2_printables.yaml` + `printables/src/prompts/`. -2. Prépare un lecteur audio avec `audio/manifests/zacus_v2_audio.yaml` et les fichiers de `game/prompts/audio/`. -3. Positionne les stations selon `kit-maitre-du-jeu/plan-stations-et-mise-en-place.md`, prépare les rôles (`distribution-des-roles.md`) et l’accueil rapide (`script-minute-par-minute.md`). -4. Flash et configure l’ESP32 en suivant `hardware/firmware/README.md` pour que l’interface réagisse aux stations imprimées. - -## Préparer l’électronique - -1. Installe PlatformIO (`pip install -U platformio`) et branche l’ESP32 décrit dans `hardware/firmware/README.md`, écran + alim. -2. Compile/flash avec un profil supporté (`pio run -e esp32dev -t upload`), puis charge les assets `pio run -e esp32dev -t uploadfs` si nécessaire. -3. Vérifie que l’écran affiche le scénario (`python3 tools/scenario/validate_scenario.py ...`), la connectique audio/led est prête, et que l’ESP32 reste branché pendant toute la partie. - -## Déroulé express -1. Accueil + immersion (0-10 min) : boucle audio d’`intro.md`, attribution des rôles, mise au courant sur les règles anti-chaos. -2. Support d’enquête (10-80 min) : station par station, fiche d’enquête, audio `incident.md`, indices printables. -3. Synthèse & final (80-105 min) : audio `accusation.md`, accusation finale, audio `solution.md`, lecture `solution-complete.md`. - -## Outils de validation -- Scénario : `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml` -- Audio : `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml` -- Printables : `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml` -- Export Markdown : `python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml` - -## Frontend V2 -- Frontend canon : `frontend-scratch-v2/`. -- Démarrage : - - `cd frontend-scratch-v2` - - `npm install` - - `VITE_STORY_API_BASE=http://:8080 npm run dev` - -## Outils test firmware - -Entrée rapide: +## Vue canonique +- Source de vérité: `game/scenarios/zacus_v2.yaml` +- Studio auteur: `frontend-scratch-v2/` +- Runtime portable: Zacus Runtime 3 +- Cible terrain principale: `hardware/firmware` sur `freenove_esp32s3` +- Shell d'orchestration: `tools/dev/zacus.sh` +## Valider le contenu ```bash -python3 tools/test/zacus_menu.py +bash tools/setup/install_validators.sh +bash tools/test/run_content_checks.sh --install-missing ``` -Codex CLI intégré : `./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md` -Préflight USB ZeroClaw : `./tools/dev/zacus.sh zeroclaw-preflight --require-port` - -Checks contenu (sans hardware): +Checks inclus: +- validation scénario +- compilation Runtime 3 +- simulation Runtime 3 +- validation runtime bundle +- validation audio +- validation printables +- export Markdown +## Compiler le runtime portable ```bash -bash tools/test/run_content_checks.sh +python3 tools/scenario/compile_runtime3.py game/scenarios/zacus_v2.yaml +python3 tools/scenario/simulate_runtime3.py game/scenarios/zacus_v2.yaml +python3 tools/scenario/verify_runtime3_pivots.py game/scenarios/zacus_v2.yaml +python3 -m unittest discover -s tests/runtime3 -p 'test_*.py' +python3 tools/scenario/export_runtime3_firmware_bundle.py game/scenarios/zacus_v2.yaml ``` -Suites série disponibles: - +Ou via le shell canonique: ```bash -python3 tools/test/run_serial_suite.py --list-suites +./tools/dev/zacus.sh runtime3-compile +./tools/dev/zacus.sh runtime3-simulate +./tools/dev/zacus.sh runtime3-verify +./tools/dev/zacus.sh runtime3-test +./tools/dev/zacus.sh frontend-test ``` -Mode laptop/CI (sans carte branchée): +Les artefacts sont écrits sous `artifacts/runtime3//`. +Le bundle firmware canonique est écrit dans `hardware/firmware/data/story/runtime3/DEFAULT.json`. +## Démarrer le studio React + Blockly ```bash -python3 tools/test/run_serial_suite.py --suite smoke_plus --allow-no-hardware -python3 tools/test/zacus_menu.py --action smoke --allow-no-hardware +cd frontend-scratch-v2 +npm install +npm test +npm run lint +VITE_STORY_API_BASE=http://:8080 npm run dev ``` -Pour résoudre les ports sans matériel, la commande `./tools/dev/zacus.sh ports` écrit le JSON contractuel dans `artifacts/ports//ports_resolve.json` et met à jour `artifacts/ports/latest_ports_resolve.json`. Pour mocker les CP2102 utilisez `ZACUS_MOCK_PORTS=1 ZACUS_PORTS_FIXTURE=tools/test/fixtures/ports_list_macos.txt ./tools/dev/zacus.sh ports`. +Pour un build local: +```bash +./tools/dev/zacus.sh frontend-build +``` -Codex CLI intégré : `./tools/dev/zacus.sh codex --prompt tools/dev/codex_prompts/zacus_overhaul_one_shot.md`. Le script note aussi l’état des ports résolus et place les logs dans `artifacts/codex//`. +## Générer la documentation +```bash +python3 -m pip install --user --break-system-packages -r tools/requirements/docs.txt +python3 -m mkdocs build --strict +``` -Guide orchestration ZeroClaw: `docs/zeroclaw_orchestration.md` +Ou: +```bash +./tools/dev/zacus.sh docs-build +``` -Dépendances optionnelles: -- `pip install pyyaml` pour `run_content_checks.sh` -- `pip install pyserial` pour suites USB, UI Link sim et console série +## Valider le firmware +```bash +cd hardware/firmware +pio run -e freenove_esp32s3 +pio run -e esp8266_oled +``` -## Pour aller plus loin -- Crée une variante : duplique le YAML, modifie `canon` et `solution` et revalide avec le script. -- Ajoute des cartes imprimables en t’inspirant des prompts dans `printables/src/prompts/`. -- Mets à jour ce quickstart via un PR si le montage change (p. ex. nouvelle station ou nouvelle plage horaire). +Chemin mono-carte recommandé: +```bash +cd hardware/firmware +ZACUS_ENV="freenove_esp32s3" ./tools/dev/run_matrix_and_smoke.sh +``` + +## Utiliser le shell Zacus +```bash +./tools/dev/zacus.sh content-checks +./tools/dev/zacus.sh ports +./tools/dev/zacus.sh artifacts-summary +./tools/dev/zacus.sh menu +``` + +Le menu utilise `gum` si disponible et repasse en mode texte sinon. + +## Où regarder ensuite +- `docs/architecture/index.md` +- `plans/master-plan.md` +- `todos/master.md` +- `hardware/firmware/docs/AGENT_TODO.md` diff --git a/docs/RC_AUTOFIX_CICD.md b/docs/RC_AUTOFIX_CICD.md deleted file mode 100644 index 3c911d7..0000000 --- a/docs/RC_AUTOFIX_CICD.md +++ /dev/null @@ -1,106 +0,0 @@ -# RC AutoFix (Scheduled) Workflow - -This document describes the GitHub Actions workflow at .github/workflows/rc-autofix-cicd.yml. - -## Trigger Manually (GitHub UI) - -1. Go to Actions tab -2. Select "RC AutoFix (Scheduled)" -3. Click "Run workflow" - -## Trigger via CLI - -```bash -gh workflow run rc-autofix-cicd.yml -``` - -## Automatic Scheduled Run - -- Runs daily at 2 AM UTC (adjust cron expression as needed) -- Can be modified in workflow file under schedule.cron - -## Environment Variables Explained - -| Variable | Value | Purpose | -|----------|-------|---------| -| ZACUS_GIT_AUTOCOMMIT | 1 | Enable automatic git commits after Codex fixes | -| ZACUS_GIT_ALLOW_WRITE | 1 | Allow git write operations (required for commits) | -| ZACUS_GIT_NO_CONFIRM | 1 | Skip confirmation prompts (required for automation) | -| ZACUS_REQUIRE_HW | 0 | Do not require physical hardware (simulation mode) | -| ZACUS_SKIP_UPLOAD | 1 | Skip ESP32/ESP8266 upload (stability + speed) | - -## Workflow Steps - -1. Checkout code from current branch -2. Setup Python and PlatformIO -3. Bootstrap Zacus environment -4. Configure Git user for commits -5. Run RC AutoFix with auto-commit enabled -6. Push changes if any commits were made -7. Upload artifacts for review (30-day retention) -8. Notify on failure (optional: configure GitHub issue comment) - -## Tracking Results - -### Via GitHub Actions UI - -- Check "Run workflow" and the latest run -- View step logs for detailed output -- Download artifacts (rc_autofix.log, codex_last_message.md, etc.) - -### Via Git History - -```bash -git log --oneline | grep "Auto-fix:" # Show all auto-fix commits -git show HEAD # Review latest commit -``` - -## Failure Modes - -| Failure | Action | -|---------|--------| -| RC pre-condition fails | Workflow continues (will fail unless fix applied) | -| Codex execution fails | Check logs in artifact/codex_last_message.md | -| Git commit fails | Check git config and repository permissions | -| Push fails | Check branch protection rules and credentials | - -## Customization - -### Change Schedule - -Edit the cron expression: - -```yaml -schedule: - - cron: '0 2 * * 1-5' # Mon-Fri at 2 AM UTC - - cron: '0 6 * * *' # Daily at 6 AM UTC -``` - -### Disable Auto-Commit - -Set ZACUS_GIT_AUTOCOMMIT: '0' (requires manual commit review). - -### Add Hardware Testing - -Set ZACUS_REQUIRE_HW: '1' (requires physical ESP32/ESP8266 and a CI runner with USB). - -### Add Slack Notification - -```yaml -- name: Notify Slack on failure - if: failure() - uses: slackapi/slack-github-action@v1.24.0 - with: - webhook-url: ${{ secrets.SLACK_WEBHOOK }} - payload: | - { - "text": "RC AutoFix failed - check artifacts" - } -``` - -## References - -- Zacus CLI: hardware/firmware/tools/dev/zacus.sh -- RC Live Codex Status: hardware/firmware/docs/RC_LIVE_CODEX_STATUS.md -- Codex Prompt Maintenance: hardware/firmware/docs/CODEX_PROMPT_MAINTENANCE.md -- Agent Briefing: .github/agents/AGENT_BRIEFINGS.md diff --git a/docs/REPO_STATE.md b/docs/REPO_STATE.md deleted file mode 100644 index 444f178..0000000 --- a/docs/REPO_STATE.md +++ /dev/null @@ -1,11 +0,0 @@ - -Repo: le-mystere-professeur-zacus -Branch: codex/repo-state-zacus -HEAD: 3fec631c43c66a6ae12c12d2f652c8800d22b28f -HeadDate: 2026-02-21T02:59:01+01:00 -HeadSubject: feat(freenove): stabilize fallback AP outside local wifi (#103) -RepoURL: https://github.com/electron-rare/le-mystere-professeur-zacus.git -ProjectKind: hardware_firmware_hybrid -PivotChanges: [{"path": "hardware/firmware/data/story/apps/APP_WIFI.json", "tags": ["firmware_build_test", "hardware_validation"]}, {"path": "hardware/firmware/docs/AGENT_TODO.md", "tags": ["firmware_build_test", "hardware_validation"]}, {"path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/include/network_manager.h", "tags": ["firmware_build_test", "hardware_validation"]}, {"path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/src/main.cpp", "tags": ["firmware_build_test", "hardware_validation"]}, {"path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/src/network_manager.cpp", "tags": ["firmware_build_test", "hardware_validation"]}] -ImpactGates: firmware_build_test, hardware_validation -GeneratedAtUTC: 2026-02-21T02:23:01Z diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..5423f0d --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,319 @@ +# Security Specification + +## Status +- State: draft +- Date: 2026-03-21 +- Supersedes: previous 4-line stub +- Related: `specs/AI_INTEGRATION_SPEC.md`, `specs/MCP_HARDWARE_SERVER_SPEC.md` + +## 1) Threat Model + +### 1.1 Deployment Context + +The Zacus escape room system operates on a **local WiFi network** in a controlled physical space. It is not exposed to the public internet. The primary attack surface is: + +| Vector | Risk | Likelihood | +|--------|------|-----------| +| Local network access (same WiFi) | Players or visitors on the game WiFi | HIGH | +| Physical access to ESP32 device | Players can touch/see the hardware | HIGH | +| Firmware extraction (USB/JTAG) | Sophisticated attacker with tools | LOW | +| Server-side (mascarade VM) | Only reachable via LAN/Tailscale | LOW | +| Supply chain (dependencies) | Compromised PlatformIO/npm package | VERY LOW | + +### 1.2 Assets to Protect + +| Asset | Sensitivity | Impact if Compromised | +|-------|------------|----------------------| +| WiFi credentials | HIGH | Network access, lateral movement | +| API bearer tokens | HIGH | Full device control | +| Game scenario content | MEDIUM | Spoilers, cheating | +| Player interaction data | LOW | Privacy (no PII collected) | +| Firmware binary | LOW | IP, reverse engineering | +| mascarade API key | HIGH | Full orchestration control | + +### 1.3 Threat Actors + +1. **Curious player**: Tries to cheat by accessing the API from their phone +2. **Tech-savvy visitor**: Scans the network, finds ESP32 endpoints +3. **Malicious local user**: Attempts injection, DoS, or firmware dump +4. **Remote attacker**: Not applicable (no internet exposure) + +## 2) API Authentication + +### 2.1 Bearer Token Scheme + +All ESP32 HTTP API endpoints require a Bearer token: + +``` +GET /api/status +Authorization: Bearer + +Response 200: { "step": "STEP_U_SON_PROTO", ... } +Response 401: { "error": "unauthorized" } +``` + +### 2.2 Token Lifecycle + +| Phase | Mechanism | +|-------|-----------| +| Generation | Random 48-char hex string (cryptographically secure) | +| Storage (ESP32) | NVS encrypted partition | +| Storage (server) | `.env` file, not committed to git | +| Provisioning | QR code scanned at setup (see 3.2) | +| Rotation | Manual, at each deployment | +| Revocation | Reflash NVS or change `.env` | + +### 2.3 Endpoint Protection Matrix + +| Endpoint | Auth Required | Rate Limit | Notes | +|----------|:------------:|:----------:|-------| +| `GET /api/status` | Yes | 10/s | Device health | +| `GET /api/scenario` | Yes | 5/s | Current state | +| `POST /api/scenario/transition` | Yes | 2/s | State change | +| `POST /api/audio` | Yes | 5/s | Audio control | +| `POST /api/led` | Yes | 10/s | LED control | +| `GET /api/camera` | Yes | 2/s | Snapshot | +| `POST /api/puzzle` | Yes | 2/s | Puzzle state | +| `GET /` | No | 20/s | Web UI (static) | +| `GET /health` | No | 20/s | Health check | +| `WS /ws` | Yes (on connect) | — | Event stream | + +### 2.4 Implementation (Firmware) + +```cpp +// Auth middleware for AsyncWebServer +bool checkAuth(AsyncWebServerRequest *request) { + if (!request->hasHeader("Authorization")) { + request->send(401, "application/json", "{\"error\":\"unauthorized\"}"); + return false; + } + String authHeader = request->header("Authorization"); + if (!authHeader.startsWith("Bearer ")) { + request->send(401, "application/json", "{\"error\":\"unauthorized\"}"); + return false; + } + String token = authHeader.substring(7); + String storedToken = nvs_get_string("auth", "api_token"); + if (token != storedToken) { + request->send(401, "application/json", "{\"error\":\"unauthorized\"}"); + return false; + } + return true; +} +``` + +## 3) WiFi Credential Management + +### 3.1 Current State (CRITICAL — FW-01) + +WiFi credentials are currently **hardcoded** in `storage_manager.cpp:73`. This must be remediated immediately. + +### 3.2 Target: NVS + QR Provisioning + +**Storage**: ESP32 NVS (Non-Volatile Storage) encrypted partition. + +**Provisioning flow**: +1. First boot: ESP32 starts in AP mode (`Zacus-Setup`) +2. Game master connects and opens captive portal +3. Game master scans QR code containing WiFi config: + ``` + WIFI:T:WPA;S:EscapeRoom-Net;P:s3cur3p4ss;; + ``` +4. ESP32 stores credentials in NVS, restarts in STA mode +5. On connection failure: fallback to AP mode after 30 s + +**NVS keys**: +| Key | Namespace | Encrypted | +|-----|-----------|:---------:| +| `wifi_ssid` | `network` | Yes | +| `wifi_pass` | `network` | Yes | +| `api_token` | `auth` | Yes | +| `device_id` | `device` | No | +| `mcp_token` | `auth` | Yes | + +### 3.3 NVS Encryption + +Enable NVS encryption with a flash encryption key: +```ini +# platformio.ini +board_build.partitions = partitions_encrypted.csv +board_build.flash_mode = dio +board_build.esp-idf.sdkconfig = + CONFIG_NVS_ENCRYPTION=y + CONFIG_SECURE_FLASH_ENC_ENABLED=y +``` + +## 4) Input Validation + +### 4.1 Requirements + +All API inputs must be validated before processing: + +| Input | Validation | +|-------|-----------| +| JSON body | Max size 4 KB, max depth 5 levels | +| `step_id` | Alphanumeric + underscore, max 64 chars, must exist in scenario | +| `event_name` | Alphanumeric + underscore, max 64 chars | +| `audio_path` | Must start with `/audio/`, no `..`, max 128 chars | +| `color` | Regex `^#[0-9a-fA-F]{6}$` or named color enum | +| `volume` | Integer 0-100 | +| `brightness` | Integer 0-255 | +| `device_id` | Alphanumeric + hyphen, max 32 chars | + +### 4.2 JSON Parsing Safety + +```cpp +// Safe JSON parsing with limits +StaticJsonDocument<4096> doc; // 4 KB max +DeserializationError err = deserializeJson(doc, body, DeserializationOption::NestingLimit(5)); +if (err) { + request->send(400, "application/json", "{\"error\":\"invalid_json\"}"); + return; +} +``` + +### 4.3 String Safety + +- All string operations use length-bounded functions (`strncpy`, `snprintf`) +- No `sprintf` or unbounded `strcpy` in firmware code +- LVGL text buffers use fixed-size arrays with null termination + +## 5) Rate Limiting + +### 5.1 Token Bucket Algorithm + +Each endpoint has a token bucket rate limiter: + +```cpp +struct RateLimiter { + uint32_t tokens; + uint32_t max_tokens; + uint32_t refill_rate_ms; // ms per token refill + uint32_t last_refill; +}; +``` + +### 5.2 Limits + +| Category | Rate | Burst | +|----------|------|-------| +| Read endpoints (GET) | 10/s | 20 | +| Write endpoints (POST) | 5/s | 10 | +| Camera capture | 2/s | 3 | +| State transitions | 2/s | 5 | +| WebSocket messages | 20/s | 50 | +| Static files | 20/s | 50 | + +### 5.3 Response on Limit + +``` +HTTP 429 Too Many Requests +Retry-After: 1 +Content-Type: application/json + +{"error": "rate_limited", "retry_after_ms": 1000} +``` + +## 6) CORS Policy + +### 6.1 Configuration + +```cpp +// CORS headers for AsyncWebServer +server.on("/*", HTTP_OPTIONS, [](AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse(204); + response->addHeader("Access-Control-Allow-Origin", allowedOrigin); + response->addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + response->addHeader("Access-Control-Allow-Headers", "Authorization, Content-Type"); + response->addHeader("Access-Control-Max-Age", "3600"); + request->send(response); +}); +``` + +### 6.2 Allowed Origins + +| Environment | Allowed Origin | +|-------------|---------------| +| Development | `http://localhost:5173` (Vite dev server) | +| Production | `http://:8080` (self-hosted UI) | +| Game master | `http://:3000` | + +Wildcard `*` is **never** used. + +## 7) Future Security Enhancements + +### 7.1 Secure Boot (Phase 2) + +Enable ESP32-S3 Secure Boot v2: +- RSA-3072 signature verification on boot +- Prevents unsigned firmware from running +- Key stored in eFuse (one-time programmable) + +```ini +# platformio.ini (future) +board_build.esp-idf.sdkconfig = + CONFIG_SECURE_BOOT=y + CONFIG_SECURE_BOOT_V2_ENABLED=y +``` + +### 7.2 Flash Encryption (Phase 2) + +Enable flash encryption to protect firmware and NVS: +- AES-256-XTS encryption +- Prevents firmware extraction via SPI flash dump +- Development mode allows re-flashing; release mode is permanent + +### 7.3 TLS / HTTPS (Phase 3) + +Upgrade HTTP API to HTTPS: +- Self-signed certificate stored in NVS +- Certificate pinning in frontend and MCP client +- Adds ~50 KB RAM overhead on ESP32 + +### 7.4 OTA Security (Phase 3) + +Signed OTA updates: +- Firmware images signed with project key +- ESP32 verifies signature before applying update +- Rollback on failed verification + +## 8) Incident Response + +### 8.1 Severity Levels + +| Level | Description | Example | +|-------|------------|---------| +| SEV-1 | Game completely compromised | Token leaked, full device control by attacker | +| SEV-2 | Security bypass discovered | Auth bypass, injection working | +| SEV-3 | Vulnerability found, not exploited | Missing validation on one endpoint | +| SEV-4 | Hardening improvement | Missing rate limit on low-risk endpoint | + +### 8.2 Response Procedure + +1. **Detect**: Monitor serial logs, API access patterns, unexpected state changes +2. **Contain**: Power off affected devices if SEV-1/SEV-2 +3. **Assess**: Identify scope (which devices, which data) +4. **Remediate**: + - Rotate all tokens immediately + - Reflash firmware with fix + - Update NVS credentials +5. **Review**: Post-incident analysis, update threat model + +### 8.3 Contact + +Security issues: open a private issue on the GitHub repository or contact the project maintainer directly. Do not disclose vulnerabilities publicly before a fix is available. + +## 9) Security Checklist (Pre-Deployment) + +- [ ] WiFi credentials stored in NVS, not in source code +- [ ] API bearer token generated and provisioned +- [ ] All API endpoints require authentication +- [ ] Input validation on all POST endpoints +- [ ] Rate limiting enabled +- [ ] CORS configured with specific origins +- [ ] No credentials in git history +- [ ] Serial debug output disabled in production build +- [ ] Firmware compiled with optimization (-Os) +- [ ] NVS encryption enabled +- [ ] Default passwords changed +- [ ] `.env` file excluded from git (in `.gitignore`) diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md index f2e5b30..8b2e285 100644 --- a/docs/STRUCTURE.md +++ b/docs/STRUCTURE.md @@ -1,196 +1,41 @@ # Repository Structure -## Overview +## Canon de refonte -This repository contains all materials for "Le Mystère du Professeur Zacus" - a complete investigation kit for birthday parties (6-14 children, 105 minutes), with a laboratory/scientific campus theme. - -## Root Structure - -``` -le-mystere-professeur-zacus/ -├── .github/ # GitHub configuration (workflows, templates) -├── audio/ # Audio manifests and prompts -├── docs/ # Documentation -├── examples/ # Example usages and demonstrations -├── game/ # Core game scenarios and rules -├── hardware/ # Firmware for microcontrollers -├── kit-maitre-du-jeu/ # Game master kit (scripts, solutions, setup) -├── ops/ # Operations scripts and utilities -├── printables/ # Printable materials (badges, cards, clues) -├── scenario-ai-coherence/# AI-generated scenario content (human-validated) -├── tools/ # Validation and export scripts -└── [root config files] # LICENSE, README, CONTRIBUTING, etc. +```mermaid +flowchart TD + YAML["game/scenarios/*.yaml"] --> Runtime3["tools/scenario Runtime 3"] + YAML --> Exports["audio / printables / kit MJ"] + Runtime3 --> Studio["frontend-scratch-v2"] + Runtime3 --> Firmware["hardware/firmware"] + Plans["memory + plans + todos"] --> Studio + Plans --> Firmware + Docs["docs/architecture + specs"] --> Plans ``` -## Detailed Directory Structure +## Répertoires principaux +- `.github/`: CI et politiques de validation. +- `audio/`: manifestes et ressources audio. +- `docs/`: quickstart, architecture, benchmark OSS, runbooks. +- `frontend-scratch-v2/`: studio auteur React + Blockly et prévisualisation Runtime 3. +- `game/`: scénarios YAML canoniques. +- `hardware/`: firmware, scripts de test terrain et documentation matériel. +- `kit-maitre-du-jeu/`: déroulé MJ, organisation des stations, scripts de partie. +- `memory/`: mémoire projet transversale. +- `plans/`: plans directeurs et plans d'agents. +- `printables/`: manifestes et sources d'indices imprimables. +- `specs/`: spécifications canoniques de la refonte. +- `todos/`: backlog opérationnel de la refonte. +- `tools/`: validateurs, compilateur/simulateur Runtime 3, shells TUI/CLI. -### `.github/` -GitHub-specific configuration files: -- **`ISSUE_TEMPLATE/`**: Issue templates for bugs and features -- **`PULL_REQUEST_TEMPLATE.md`**: Pull request template -- **`workflows/`**: CI/CD workflows for validation +## Règles de structure +- `game/scenarios/*.yaml` reste la source de vérité narrative. +- `hardware/firmware/esp32/` est en lecture seule. +- `frontend-scratch-v2/` est le front canonique; les chemins legacy doivent sortir du flux principal. +- Les artefacts d'exécution restent hors git et sont référencés depuis les TODO ou rapports. -### `audio/` -Audio content organization: -- **`manifests/`**: YAML manifests describing audio requirements -- **`game/prompts/audio/`**: Audio generation prompts (intro, incidents, solutions) - -Validation: `python3 tools/audio/validate_manifest.py` - -### `docs/` -Comprehensive documentation: -- **`BRANCHES.md`**: Git branch strategy and workflows -- **`STRUCTURE.md`**: This file - repository architecture -- **`QUICKSTART.md`**: Express setup guide -- **`STYLEGUIDE.md`**: Writing style and tone guidelines -- **`WORKFLOWS.md`**: Development workflows -- **`GLOSSARY.md`**: Terms and definitions -- **`index.md`**: Documentation index -- **`repo-status.md`**: Current repository status -- **`maintenance-repo.md`**: Maintenance procedures -- **`repo-audit.md`**: Audit reports -- **`_generated/`**: Auto-generated documentation -- **`assets/`**: Documentation images and diagrams - -### `examples/` -Cross-functional examples and demonstrations. See `examples/README.md` for index. - -### `game/` -Core game content (canonical): -- **`scenarios/`**: YAML scenario definitions (single source of truth) - - `zacus_v2.yaml`: Canon scenario with acts, runtime steps and terrain clues -- **`prompts/`**: Generation prompts for various content -- **`exports/`**: Generated exports (not versioned) - -Validation: `python3 tools/scenario/validate_scenario.py` - -### `hardware/` -Microcontroller firmware and hardware integration: -- **`firmware/`**: - - `esp32/`: ESP32 firmware (main controller, WiFi, audio, story engine) - - `arduino/`: Arduino-compatible variants - - `README.md`: Branch strategy and setup - -**Note**: Firmware uses Git branches for development variants instead of separate directories: -- `main:hardware/firmware/` → stable releases -- `hardware/esp32-dev` → active development -- `hardware/esp32-experimental` → R&D - -See `hardware/firmware/README.md` for complete details. - -### `kit-maitre-du-jeu/` -Game Master materials: -- Minute-by-minute script (ready-to-play) -- Complete solution (culprit, motive, method, timeline) -- Material checklist and 4-station setup -- Role distribution (modular for 6-14 children) -- Anti-chaos guide and station map -- PDF exports - -### `ops/` -Operational scripts: -- **`check_scope.sh`**: Validate branch scope compliance -- **`codex_story_ia.sh`**: Story-IA workflow helper - -### `printables/` -Printable materials for the game: -- **`src/`**: Source files and generation prompts -- **`export/pdf/`**: Exported PDFs (locally generated, not versioned) -- **`export/png/`**: Exported PNGs (locally generated, not versioned) -- **`manifests/`**: YAML manifests for printables validation -- **`WORKFLOW.md`**: Generation and export workflow - -Validation: `python3 tools/printables/validate_manifest.py` - -### `scenario-ai-coherence/` -AI-generated scenario coherence artifacts (human-validated): -- **`version-finale/`**: Validated final content -- Future: intermediate versions, generation logs - -This folder contains AI-generated scenario content that has been reviewed and approved by human maintainers. - -### `tools/` -Python validation and export scripts: -- **`scenario/`**: Scenario validation and export - - `validate_scenario.py`: Validate YAML scenarios - - `export_md.py`: Generate markdown from scenarios -- **`audio/`**: Audio manifest validation - - `validate_manifest.py`: Validate audio manifests -- **`printables/`**: Printables manifest validation - - `validate_manifest.py`: Validate printables manifests -- **`qa/`**: Quality assurance utilities (optional for firmware) - -### Root Configuration Files - -- **`README.md`**: Main project overview -- **`LICENSE.md`**: Dual licensing (MIT for code, CC BY-NC 4.0 for content) -- **`CONTRIBUTING.md`**: Contribution guidelines -- **`CODEX_RULES.md`**: Branch scope rules for AI assistants -- **`CHANGELOG.md`**: Version history -- **`AGENTS.md`**: AI agent descriptions -- **`SECURITY.md`**: Security policy -- **`CODE_OF_CONDUCT.md`**: Community standards -- **`DISCLAIMER.md`**: Legal disclaimer -- **`.gitignore`**: Git ignore patterns -- **`Makefile`**: Build and validation commands - -## Data Flow - -``` -game/scenarios/*.yaml (CANON) - ↓ -tools/scenario/validate_scenario.py → validation - ↓ -tools/scenario/export_md.py → docs/_generated/ - ↓ -printables/src/ + audio/prompts/ → printable PDFs + audio files - ↓ -kit-maitre-du-jeu/ → complete game master package -``` - -## Branch Strategy - -See `BRANCHES.md` for complete branch workflow. Key branches: -- **`main`**: Stable integration branch -- **`hardware/firmware`**: Firmware development -- **`generation/story-esp`**: Story engine for ESP32 -- **`generation/story-ia`**: Game content and scenarios -- **`scripts/generation`**: Validation and export tools -- **`documentation`**: Documentation updates - -## Validation Workflow - -```bash -# Validate scenario -python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v2.yaml - -# Export markdown -python3 tools/scenario/export_md.py game/scenarios/zacus_v2.yaml - -# Validate audio manifest -python3 tools/audio/validate_manifest.py audio/manifests/zacus_v2_audio.yaml - -# Validate printables manifest -python3 tools/printables/validate_manifest.py printables/manifests/zacus_v2_printables.yaml - -# Or use Makefile -make scenario-validate -make export -make audio-validate -make printables-validate -make all-validate -``` - -## Licenses - -- **Code** (firmware, scripts, tools): MIT License -- **Creative content** (documents, PDFs, assets, prompts): CC BY-NC 4.0 -- See `LICENSE.md` and `LICENSES/` for full text - -## Contributing - -See `CONTRIBUTING.md` for contribution guidelines and review process. - -## Status - -Current project status tracked in `docs/repo-status.md`. +## Chemins de référence +- Architecture: `docs/architecture/index.md` +- Runtime: `specs/ZACUS_RUNTIME_3_SPEC.md` +- Studio: `specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md` +- Pilotage: `plans/master-plan.md`, `todos/master.md` diff --git a/docs/architecture/agent-matrix.md b/docs/architecture/agent-matrix.md new file mode 100644 index 0000000..ae54d30 --- /dev/null +++ b/docs/architecture/agent-matrix.md @@ -0,0 +1,17 @@ +# Agent Matrix + +| Agent | Scope | Inputs | Outputs | +| --- | --- | --- | --- | +| PM / Architecture | Global audit, sequencing, dependency map | repo state, plans, docs | `plans/master-plan.md`, architecture maps | +| Runtime / Compiler | Runtime 3 schema, compiler, simulator | YAML scenarios, runtime spec | IR JSON, simulator traces, migration rules | +| Frontend Studio | React + Blockly authoring studio | Runtime 3 contract, API contract | Studio UI, previews, lint/build gates | +| Firmware Adapter | Runtime execution on device | IR JSON, firmware APIs | buildable firmware, runtime adapter | +| Content / Narrative | Scenario migration and feature cards | YAML canon, audio/printables manifests | consistent scenario graph, content backlog | +| Tooling / TUI / Logs | CLI/TUI wrappers and evidence | scripts, CI, artifact dirs | `tools/dev/zacus.sh`, validation entrypoints | +| Docs / Knowledge | Specs, Mermaid maps, READMEs | all workstreams | updated docs, MkDocs site | +| QA / Release | Gates, proofs, release readiness | builds, logs, simulations | validation matrix, release checklist | + +## Coordination Rule +- `memory/project-memory.md` stores cross-wave context. +- `plans/agents/*.md` store agent-specific execution plans. +- `todos/*.md` store the next concrete chain of work. diff --git a/docs/architecture/ai-integration-map.md b/docs/architecture/ai-integration-map.md new file mode 100644 index 0000000..7e40e58 --- /dev/null +++ b/docs/architecture/ai-integration-map.md @@ -0,0 +1,235 @@ +# AI Integration Map + +## Mindmap — AI Capabilities + +```mermaid +mindmap + root((Zacus AI)) + On-Device + ESP-SR v2.0 + WakeNet "Hey Zacus" + MultiNet 50 commands FR + 16-bit PCM 16kHz + 280 KB PSRAM + ESP-DL v3.2 + YOLOv11n INT8 + 8 prop classes + 5-7 FPS 320x240 + 600 KB PSRAM + Fallback + Tap-to-talk UI + QR code scan + Pre-recorded hints + Server + mascarade LLM + Adaptive hints + Anti-spoiler prompts + Conversation memory + Ollama backend + Coqui XTTS-v2 + Voice cloning 6s ref + French TTS + Chunked streaming + Docker container + MCP Server + puzzle_set_state + audio_play + led_set + camera_capture + scenario_advance + GPU KXKM-AI + AudioCraft MusicGen + Room ambients 30-60s + SFX generation + Batch pre-generation + MP3 128kbps output + Fine-tune Pipeline + Qwen2.5-Coder-1.5B + Unsloth + SimPO + GGUF Q4_K_M export + P2P distribute_task + Analytics + Difficulty adaptation + Step timing telemetry + Hint usage tracking + Player count detection +``` + +## Flowchart — Data Flows + +```mermaid +flowchart TD + subgraph Input["Player Input"] + MIC[Microphone I2S] + CAM[Camera OV2640] + BTN[Buttons / Touch] + QR[QR Scanner] + end + + subgraph OnDevice["ESP32-S3 On-Device AI"] + SR[ESP-SR v2.0
Wake + Commands] + DL[ESP-DL v3.2
Object Detection] + RT3[Runtime 3 Engine
Step Machine] + AUD[Audio Manager
I2S DMA] + LED[LED Manager
WS2812B] + UI[LVGL Display] + end + + subgraph Network["Network Layer"] + WIFI[WiFi HTTP Client] + ESPNOW[ESP-NOW Mesh] + WS[WebSocket Events] + end + + subgraph Server["mascarade Server"] + API[mascarade API
/api/v1/send] + MCP[MCP Hardware Server
JSON-RPC 2.0] + TTS[Coqui XTTS-v2
Docker :5002] + OLLAMA[Ollama
:11434] + ADAPT[Adaptive Engine
Difficulty Params] + end + + subgraph GPU["KXKM-AI RTX 4090"] + MUSICGEN[AudioCraft MusicGen
Ambient Generation] + STABAUD[Stable Audio Open
SFX Generation] + FINETUNE[Fine-tune Pipeline
Unsloth] + end + + subgraph Output["Player Output"] + SPK[Speaker I2S] + LEDS[LED Strips] + SCREEN[LCD Display] + SERVO[Puzzle Actuators] + end + + %% Input -> On-Device + MIC -->|PCM 16kHz| SR + CAM -->|320x240 RGB| DL + BTN -->|GPIO events| RT3 + QR -->|decoded text| RT3 + + %% On-Device processing + SR -->|command token| RT3 + DL -->|detection event| RT3 + RT3 -->|play command| AUD + RT3 -->|color command| LED + RT3 -->|screen update| UI + + %% On-Device -> Network + SR -->|complex query| WIFI + RT3 -->|hint request| WIFI + RT3 -->|telemetry| WIFI + RT3 <-->|peer sync| ESPNOW + RT3 -->|state events| WS + + %% Network -> Server + WIFI -->|POST /api/v1/send| API + WIFI -->|JSON-RPC| MCP + WS -->|status stream| MCP + + %% Server processing + API -->|inference| OLLAMA + OLLAMA -->|hint text| API + API -->|text| TTS + MCP -->|tool dispatch| API + API -->|telemetry| ADAPT + ADAPT -->|params| API + + %% Server -> Device + TTS -->|PCM stream| AUD + MCP -->|puzzle cmd| SERVO + MCP -->|led cmd| LED + MCP -->|audio cmd| AUD + + %% GPU -> Server (offline) + MUSICGEN -.->|ambient MP3| AUD + STABAUD -.->|SFX WAV| AUD + FINETUNE -.->|GGUF model| OLLAMA + + %% Output + AUD --> SPK + LED --> LEDS + UI --> SCREEN + RT3 --> SERVO + + %% Styling + style OnDevice fill:#1a3a1a,color:#fff + style Server fill:#1a1a3a,color:#fff + style GPU fill:#3a1a1a,color:#fff +``` + +## Latency Map + +```mermaid +flowchart LR + A["Player speaks"] -->|0ms| B["ESP-SR wake"] + B -->|200ms| C["Command parse"] + C -->|500ms| D{"On-device
or server?"} + D -->|"on-device
0ms"| E["Runtime 3 action"] + D -->|"server
50ms network"| F["mascarade LLM"] + F -->|"2000ms inference"| G["TTS synthesis"] + G -->|"2000ms render"| H["Audio stream"] + H -->|"100ms buffer"| I["Speaker plays"] + + E -->|"50ms"| J["LED/Audio/Servo"] + + style D fill:#dd6,stroke:#333 +``` + +**Critical path (voice hint)**: 200 + 500 + 50 + 2000 + 2000 + 100 = ~4850 ms worst case, target < 3000 ms. + +## Component Integration Matrix + +```mermaid +flowchart TD + subgraph Legend + direction LR + L1[Implemented] --- L2[Planned Phase B] + L2 --- L3[Planned Phase C+] + end + + subgraph Current["Currently Implemented"] + R3[Runtime 3 Engine] + AUDIO[Audio Manager I2S] + LEDM[LED Manager] + LVGL[LVGL UI] + WIFIC[WiFi + HTTP API] + ESPNM[ESP-NOW Manager] + STOR[Storage Manager] + end + + subgraph PhaseB["Phase B: Voice"] + WAKE[WakeNet Custom] + MULTI[MultiNet FR] + XTTS[XTTS-v2 Docker] + STREAM[Audio Streaming] + end + + subgraph PhaseC["Phase C+: Vision, LLM, Music"] + YOLO[YOLOv11n ESP-DL] + HINTS[LLM Hints] + MCPS[MCP Server] + MGEN[MusicGen Batch] + end + + R3 --> WAKE + R3 --> YOLO + R3 --> HINTS + AUDIO --> STREAM + STREAM --> XTTS + WIFIC --> MCPS + MCPS --> HINTS + HINTS --> XTTS + AUDIO --> MGEN + WAKE --> MULTI + + style Current fill:#2d6,stroke:#333 + style PhaseB fill:#69d,stroke:#333 + style PhaseC fill:#d69,stroke:#333 +``` + +## Notes +- On-device AI (ESP-SR, ESP-DL) runs without network — zero-latency fallback. +- Server AI (LLM, TTS, MCP) requires WiFi — graceful degradation to cached hints. +- GPU AI (AudioCraft) is offline batch only — no runtime dependency. +- All AI features are additive: the base Runtime 3 game works without any AI. diff --git a/docs/architecture/component-map.md b/docs/architecture/component-map.md new file mode 100644 index 0000000..48296e8 --- /dev/null +++ b/docs/architecture/component-map.md @@ -0,0 +1,45 @@ +# Component Map + +```mermaid +flowchart LR + subgraph Studio + App["App.tsx"] + Designer["BlocklyDesigner.tsx"] + ScenarioLib["lib/scenario.ts"] + Runtime3Lib["lib/runtime3.ts"] + ApiLib["lib/api.ts"] + end + + subgraph Canon + ScenarioYaml["game/scenarios/zacus_v2.yaml"] + end + + subgraph Runtime3 + CompilerPy["tools/scenario/compile_runtime3.py"] + RuntimeCommon["tools/scenario/runtime3_common.py"] + SimPy["tools/scenario/simulate_runtime3.py"] + end + + subgraph Device + Firmware["hardware/firmware"] + StoryCore["lib/story runtime adapter"] + UI["Freenove UI + API"] + end + + ScenarioYaml --> ScenarioLib + Designer --> ScenarioLib + ScenarioLib --> Runtime3Lib + Runtime3Lib --> App + ScenarioYaml --> CompilerPy + CompilerPy --> RuntimeCommon + RuntimeCommon --> SimPy + RuntimeCommon --> Firmware + Firmware --> StoryCore + StoryCore --> UI + ApiLib --> UI +``` + +## Notes +- The React studio owns authoring ergonomics and previews. +- The Python compiler/simulator owns deterministic offline validation. +- Firmware consumes the Runtime 3 contract and exposes runtime APIs. diff --git a/docs/architecture/data-flow-map.md b/docs/architecture/data-flow-map.md new file mode 100644 index 0000000..33a36f9 --- /dev/null +++ b/docs/architecture/data-flow-map.md @@ -0,0 +1,21 @@ +# Data Flow Map + +```mermaid +flowchart TD + Author["Author edits Blockly graph"] --> Draft["Scenario draft in browser"] + Draft --> Yaml["Canonical YAML"] + Yaml --> Validate["Scenario validators"] + Yaml --> Compile["Runtime 3 compiler"] + Compile --> IR["Runtime 3 JSON"] + IR --> Sim["Local simulator"] + IR --> Firmware["Firmware adapter"] + Yaml --> Exports["Audio / printables / MJ kit exports"] + Firmware --> Api["Runtime API + status"] + Api --> Studio["Studio dashboard + diagnostics"] + Api --> Evidence["Logs / artifacts / AGENT_TODO evidence"] +``` + +## Guarantees +- Narrative truth remains in YAML during the migration window. +- Runtime truth is emitted as versioned IR JSON. +- Hardware evidence stays outside git and is referenced from planning/TODO docs. diff --git a/docs/architecture/feature-map.md b/docs/architecture/feature-map.md new file mode 100644 index 0000000..b012258 --- /dev/null +++ b/docs/architecture/feature-map.md @@ -0,0 +1,34 @@ +# Feature Map + +## Core Gameplay Features + +| Feature | Canon input | Runtime pivot | Delivery surfaces | +| --- | --- | --- | --- | +| LA 440 stabilization | `puzzles.PUZZLE_LA_440` | `STEP_LA_DETECTOR` | studio, firmware, audio, GM kit | +| LEFOU piano unlock | `puzzles.PUZZLE_PIANO_ALPHABET_5` | `STEP_LEFOU_DETECTOR` | studio, firmware, printables, GM kit | +| QR WIN finale | `puzzles.PUZZLE_QR_WIN` | `STEP_QR_DETECTOR` | studio, firmware camera, archives setup | +| Media hub / finale | `firmware.media_hub` | `SCENE_MEDIA_MANAGER` | firmware, studio dashboard | + +## Authoring Features + +```mermaid +mindmap + root((Studio Zacus)) + Blockly editor + Steps + Transitions + Apps + YAML preview + Runtime 3 preview + Validate + Deploy + Dashboard + Media + Network +``` + +## Refactor Priorities +1. Authoring parity with the current canonical scenario. +2. Runtime 3 compilation and simulation. +3. Combined-board execution on Freenove. +4. Documentation and cleanup of legacy references. diff --git a/docs/architecture/index.md b/docs/architecture/index.md new file mode 100644 index 0000000..10807a9 --- /dev/null +++ b/docs/architecture/index.md @@ -0,0 +1,33 @@ +# Zacus Architecture + +Cette section est le point d’entrée canonique de la refonte Zacus V3. + +## Vue d’ensemble +- Le contenu narratif reste défini dans `game/scenarios/*.yaml`. +- Le studio auteur canonique est `frontend-scratch-v2/`. +- Le nouveau noyau cible est **Zacus Runtime 3**, compilé en IR JSON. +- Le firmware devient un adaptateur d’exécution et d’API. + +## Cartes publiées +- [System Map](system-map.md) +- [Component Map](component-map.md) +- [Data Flow Map](data-flow-map.md) +- [Feature Map](feature-map.md) +- [Migration Map](migration-map.md) +- [Agent Matrix](agent-matrix.md) +- [Release Map](release-map.md) + +## Principe directeur +Une seule architecture visible: + +```mermaid +flowchart LR + Author["Auteur Zacus"] --> Studio["Studio React + Blockly"] + Studio --> Canon["YAML canonique"] + Canon --> Compiler["Compilateur Runtime 3"] + Compiler --> IR["IR JSON versionné"] + IR --> Firmware["Adaptateur firmware"] + Firmware --> Device["Carte Freenove / périphériques"] + Firmware --> API["API runtime + observabilité"] + API --> Studio +``` diff --git a/docs/architecture/migration-map.md b/docs/architecture/migration-map.md new file mode 100644 index 0000000..0eccced --- /dev/null +++ b/docs/architecture/migration-map.md @@ -0,0 +1,23 @@ +# Migration Map + +## Migration Route + +```mermaid +flowchart LR + LegacyDocs["Conflicting docs/specs"] --> CanonDocs["Unified architecture docs"] + LegacyFront["fronted dev web UI"] --> ReactStudio["frontend-scratch-v2"] + StoryV2["Story V2 runtime"] --> Runtime3["Zacus Runtime 3"] + DirectFirmwareStory["Firmware-owned story semantics"] --> FirmwareAdapter["Firmware adapter over IR"] + AdHocTracking["Scattered TODO/plan refs"] --> RefactorCoord["memory/ + plans/ + todos/"] +``` + +## Rules +- No destructive cleanup before replacement proof. +- Legacy routes are quarantined first, then removed. +- Hardware evidence stays in `hardware/firmware/docs/AGENT_TODO.md` until the adapter route is stable. + +## Current Quarantine Targets +- `fronted dev web UI/` +- Svelte/Cytoscape specs and references +- `zacus_v1` references outside archive or historical context +- duplicate docs such as `docs/AGENTS 2.md` and `docs/AGENT_TODO 2.md` diff --git a/docs/architecture/release-map.md b/docs/architecture/release-map.md new file mode 100644 index 0000000..d3f0401 --- /dev/null +++ b/docs/architecture/release-map.md @@ -0,0 +1,20 @@ +# Release Map + +```mermaid +flowchart LR + Wave0["Wave 0 Baseline"] --> Wave1["Wave 1 Canon"] + Wave1 --> Wave2["Wave 2 Runtime 3"] + Wave2 --> Wave3["Wave 3 Studio"] + Wave3 --> Wave4["Wave 4 Firmware"] + Wave4 --> Wave5["Wave 5 Cleanup"] + Wave5 --> Wave6["Wave 6 Cutover"] +``` + +## Exit Criteria +- Wave 0: baseline evidence captured and dirty-tree risks understood. +- Wave 1: architecture/spec/docs/plans canonized. +- Wave 2: Runtime 3 compiler and simulator usable on the canonical scenario. +- Wave 3: React/Blockly studio builds and previews Runtime 3. +- Wave 4: Freenove build path consumes the runtime contract without regression. +- Wave 5: legacy paths archived or deleted with proof of replacement. +- Wave 6: README, docs site, CI, and operator entrypoints all point to the new canon. diff --git a/docs/architecture/system-map.md b/docs/architecture/system-map.md new file mode 100644 index 0000000..51538ad --- /dev/null +++ b/docs/architecture/system-map.md @@ -0,0 +1,48 @@ +# System Map + +## Canonical System + +```mermaid +flowchart TD + subgraph Authoring + YAML["game/scenarios/zacus_v2.yaml"] + Blockly["frontend-scratch-v2 Blockly graph"] + end + + subgraph Runtime3 + Compiler["compile_runtime3.py / runtime3.ts"] + IR["Runtime 3 IR JSON"] + Sim["simulate_runtime3.py"] + end + + subgraph Delivery + Audio["audio/manifests"] + Printables["printables/manifests"] + Kit["kit-maitre-du-jeu"] + Docs["docs + MkDocs"] + end + + subgraph Device + Firmware["hardware/firmware adapter"] + Board["freenove_esp32s3"] + Media["media / network / UI"] + end + + YAML --> Compiler + Blockly --> Compiler + Compiler --> IR + IR --> Sim + IR --> Firmware + YAML --> Audio + YAML --> Printables + YAML --> Kit + YAML --> Docs + Firmware --> Board + Firmware --> Media +``` + +## Design Notes +- YAML stays canonical for narrative truth during migration. +- Blockly is the preferred authoring UX, not the runtime artifact itself. +- Runtime 3 IR is the portable contract between authoring and execution. +- Firmware owns board integration, not story semantics. diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png deleted file mode 100644 index 1d2e82e..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png deleted file mode 100644 index a9388f5..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png deleted file mode 100644 index 5410f07..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png deleted file mode 100644 index 6b1d729..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png deleted file mode 100644 index 4d9a71a..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png deleted file mode 100644 index 9daee61..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png deleted file mode 100644 index 8346f44..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png deleted file mode 100644 index 1d2e82e..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_07.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png deleted file mode 100644 index a9388f5..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_10.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png deleted file mode 100644 index 5410f07..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_09_14.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png deleted file mode 100644 index 6b1d729..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_38.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png deleted file mode 100644 index 4d9a71a..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_41.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png deleted file mode 100644 index 9daee61..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_12_44.png and /dev/null differ diff --git a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png b/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png deleted file mode 100644 index 8346f44..0000000 Binary files a/docs/assets/ChatGPT Image 25 févr. 2026 à 19_46_57.png and /dev/null differ diff --git a/docs/benchmark-oss.md b/docs/benchmark-oss.md new file mode 100644 index 0000000..831da14 --- /dev/null +++ b/docs/benchmark-oss.md @@ -0,0 +1,31 @@ +# OSS Benchmark + +## Adopt / Align + +| Tool | Why it matters | Reference | +| --- | --- | --- | +| Blockly | Canonical block-based authoring model for the Zacus studio. | https://developers.google.com/blockly | +| Mermaid | Lightweight diagrams embedded in docs and specs. | https://mermaid.js.org/ | +| Material for MkDocs | Fast documentation shell for the rebuilt knowledge base. | https://squidfunk.github.io/mkdocs-material/ | +| Charm Gum | Optional shell TUI layer with graceful fallback. | https://github.com/charmbracelet/gum | + +## Benchmark / Inspiration + +| Project | Why it is relevant | Reference | +| --- | --- | --- | +| Node-RED | Flow-based authoring patterns and graph ergonomics. | https://nodered.org/ | +| Ink | Narrative authoring and branching conventions. | https://www.inklestudios.com/ink/ | +| Yarn Spinner | Dialogue/runtime separation patterns. | https://www.yarnspinner.dev/ | +| LVGL | Embedded UI runtime constraints and display patterns. | https://lvgl.io/ | +| SquareLine Studio | Embedded UI workflow inspiration for screen authoring. | https://squareline.io/ | +| EEZ Studio | Embedded UI/editor workflow benchmark for device tooling. | https://www.envox.eu/studio/studio-introduction/ | + +## Zacus-specific Decision +- Zacus Runtime 3 remains custom and portable. +- External OSS is used as a reference or shell, not as a drop-in replacement for the story runtime. + +## Fresh Notes (2026-03-16) +- Blockly remains the strongest fit for Zacus authoring because it is still positioned as a customizable client-side block editor for application-specific languages. +- Node-RED remains the best ergonomics benchmark for event-driven flows that can run both on low-cost edge devices and in the cloud. +- LVGL stays the right embedded UI reference because it explicitly targets small memory footprints and broad MCU/OS/display portability. +- EEZ Studio is the strongest workflow benchmark for embedded authoring because it combines templates, visual debugging, execution logs, and timeline-driven UI tooling. diff --git a/docs/index.md b/docs/index.md index dd4205f..b64e58e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,119 +1,31 @@ ---- -layout: default -title: Le Mystère du Professeur Zacus -description: Une enquête scientifique immersive pour anniversaire (printables, audio, modules électroniques). ---- +# Zacus V3 -# 🎩 Le Mystère du Professeur Zacus +Point d'entrée documentaire de la refonte Zacus. -![Couverture](./assets/cover.png) +## Canon +- YAML source de vérité: `game/scenarios/zacus_v2.yaml` +- Studio auteur: `frontend-scratch-v2/` +- Runtime portable: Zacus Runtime 3 +- Cible terrain: Freenove ESP32-S3 via `hardware/firmware/` -Le Mystère du Professeur Zacus : une enquête scientifique immersive pour anniversaire, jouable en famille ou entre ami·e·s. +## Démarrage +- [Quickstart](QUICKSTART.md) +- [Architecture](architecture/index.md) +- [Benchmark OSS](benchmark-oss.md) -Scénarios modulaires (YAML), supports imprimables, audio, et accessoires électroniques (ESP32/Arduino). +## Carte rapide -
-
hatScénarios modulaires
-
audioAudio & checkpoints
-
chipModules électroniques
-
flaskThème labo/science
-
- ---- - -## Structure du projet - -```text -├── game/scenarios/*.yaml Scénarios (source de vérité) -├── audio/manifests/*.yaml Manifeste audio + fichiers associés -├── printables/manifests/*.yaml Manifeste printables + PDF/PNG -├── hardware/firmware/esp32/ Firmware (lecture seule sans approbation) -├── tools/ Outils Python (validation, export, génération) -├── docs/ Guides + mini-site GitHub Pages -├── assets/ Images (README / marketing) +```mermaid +flowchart LR + YAML["Scenario YAML"] --> Runtime3["Runtime 3"] + Runtime3 --> Studio["React + Blockly studio"] + Runtime3 --> Firmware["Firmware adapter"] + YAML --> Exports["Audio / printables / MJ kit"] ``` ---- - -## Démarrage rapide - -1. Imprime les printables (cartes, indices). -2. (Option) Flash les modules électroniques. -3. Lis le scénario YAML et place les indices. -4. Lance la partie et suis le guide MJ. - ---- - -## Guides & documentation - -- [STRUCTURE.md](STRUCTURE.md) : Architecture détaillée -- [QUICKSTART.md](QUICKSTART.md) : Démarrage express -- [WORKFLOWS.md](WORKFLOWS.md) : Workflows validation/export -- [faq.md](faq.md) : FAQ dépannage - ---- - -## Licences - -- Code : MIT — voir [../LICENSE](../LICENSE) -- Contenu créatif : CC BY‑NC 4.0 — voir [../LICENSE-CONTENT.md](../LICENSE-CONTENT.md) - ---- - -## Assets - -- Couverture : assets/cover.png -- OpenGraph : assets/og.png -- Poster : assets/poster.png -- Logo : assets/logo.png -- Diagramme : assets/diagram.png -- Aperçu printables : assets/printables.png - -## Démo - -![Démo](./assets/demo.gif) - ---- - -## Jouer (MJ) - -### Checklist rapide -1. Imprime les **printables** (cartes, indices). -2. (Option) Flash les modules électroniques. -3. Lis le scénario et place les indices. -4. Lance la partie et suis le guide MJ. - -👉 Voir aussi : [FAQ (flash / SD / audio)](./faq) - ---- - -## Développer / contribuer - -### Structure du dépôt -- `game/scenarios/` — scénarios (source de vérité) -- `audio/` — manifestes et ressources audio -- `printables/` — manifestes + PDF/PNG -- `tools/` — outils Python (validation, export) -- `hardware/firmware/esp32/` — firmware (**lecture seule** sans approbation) - -### Démarrage rapide -```bash -python3 -m pip install -r tools/requirements.txt -python3 tools/scenario/validate_scenario.py game/scenarios/mon_scenario.yaml -``` - ---- - -## Visuels & matériel - -![Prototype](./assets/board.jpg) - -![Printables](./assets/printables.png) - -![Diagramme](./assets/diagram.png) - ---- - -## Licences -- **Code** : MIT (`LICENSE`) -- **Contenu** : CC BY‑NC 4.0 (`LICENSE-CONTENT.md`) +## Liens utiles +- [Repository Structure](STRUCTURE.md) +- Spécification Runtime 3: `specs/ZACUS_RUNTIME_3_SPEC.md` +- Spécification studio: `specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md` +- Plans: `plans/master-plan.md` +- Todos: `todos/master.md` diff --git a/docs/repo-audit.md b/docs/repo-audit.md deleted file mode 100644 index b49830d..0000000 --- a/docs/repo-audit.md +++ /dev/null @@ -1,67 +0,0 @@ -# Audit de cohérence du dépôt - -Date: 2026-02-12 (mise à jour cohérence globale) -Périmètre: **contenu documentaire + structure fichiers**, sans modification firmware. - -## Résumé exécutif - -- Le dépôt est globalement bien structuré en 3 piliers: `kit-maitre-du-jeu/`, `printables/`, `hardware/`. -- La zone la plus mature côté exécution est le firmware ESP32/ESP8266 (documenté et outillé). -- Le principal point de cohérence identifié concernait les chemins printables documentés mais absents. -- Les images PNG printables sont désormais centralisées sous `printables/export/png/`. - -## Couverture de revue - -- Revue des fichiers Markdown/README du dépôt (hors licences externes inchangées). -- Vérification de cohérence des chemins documentés vs arborescence versionnée. -- Vérification des assets image versionnés. - -## Analyse du contenu des fichiers - -### 1) Documentation racine -- `README.md` décrit clairement le périmètre et les licences. -- `CONTRIBUTING.md` couvre les règles éditoriales et le workflow. -- `CHANGELOG.md` contient désormais une release `0.2.0` et un `Unreleased` exploitable. - -### 2) Kit MJ -- Le dossier `kit-maitre-du-jeu/` est cohérent et orienté animation terrain. -- Les sous-docs couvrent script, solution, rôles, anti-chaos et stations. - -### 3) Printables -- La convention de nommage est explicite. -- Le dépôt déclarait une structure `src/export` non matérialisée: ce point est désormais aligné. - -### 4) Hardware -- Arborescence hardware lisible (`bom`, `wiring`, `firmware`, `enclosure`). -- Le firmware reste hors périmètre de modification dans ce cycle. - -## Analyse des images - -Commande utilisée: -- `rg --files | rg -i '\\.(png|jpe?g|gif|webp|svg|bmp)$'` - -Résultat: -- Des previews PNG sont bien versionnées, avec un classement thématique dans `printables/export/png/`. - -Conséquence: -- La revue visuelle des imprimables est possible directement depuis le dépôt. -- Recommandé: maintenir ce classement à chaque nouvel export. - -## Corrections de cohérence appliquées - -1. Création de la structure printables attendue: - - `printables/src/` - - `printables/export/pdf/` - - `printables/export/png/` -2. Création des sous-dossiers invitations cohérents avec son README: - - `printables/invitations/src/` - - `printables/invitations/export/pdf/` - - `printables/invitations/export/png/` -3. Ajout de README ciblés dans les nouveaux dossiers printables. -4. Regroupement des PNG du dossier `printables/` vers `printables/export/png/` avec sous-dossiers (`general`, `fiche-enquete`, `personnages`, `zones`). - -## Recommandations immédiates (hors firmware) - -- Ajouter 1 à 3 documents pilotes dans `printables/export/pdf/` et leurs previews dans `printables/export/png/`. -- Maintenir `CHANGELOG.md` à chaque PR doc-only. -- Utiliser la checklist PR de `CONTRIBUTING.md` comme gate de merge. diff --git a/docs/repo-deep-analysis-2026-02-25.md b/docs/repo-deep-analysis-2026-02-25.md deleted file mode 100644 index c94ec84..0000000 --- a/docs/repo-deep-analysis-2026-02-25.md +++ /dev/null @@ -1,54 +0,0 @@ -# Analyse approfondie du dépôt — 2026-02-25 - -## Objectif -Aller plus loin que le lot conversation bundle en évaluant l'état global du repo (docs, workflows, contenu gameplay, outillage validation, CI). - -## Méthode -Commandes de revue utilisées (échantillon): -- `rg --files` -- `rg -n "validate_|workflow|scenario" .github/workflows tools docs game` -- `python3 tools/scenario/validate_runtime_bundle.py` -- `python3 tools/scenario/validate_scenario.py game/scenarios/zacus_v1.yaml` -- `python3 tools/scenario/export_md.py game/scenarios/zacus_v1.yaml` -- `python3 tools/audio/validate_manifest.py audio/manifests/zacus_v1_audio.yaml` -- `python3 tools/printables/validate_manifest.py printables/manifests/zacus_v1_printables.yaml` - -## Constat global - -### 1) Source de vérité gameplay -- ✅ `game/scenarios/zacus_v1.yaml` est bien utilisé comme source canon. -- ✅ Les exports MJ/docs générés sont cohérents avec le canon courant. -- ⚠️ Le bundle `scenario-ai-coherence/` reste parallèle: utile pour itérer, mais il faut éviter toute dérive silencieuse. - -### 2) Gates G3 / validation -- ✅ Validateurs scénario/audio/printables présents et opérationnels. -- ✅ `validate_runtime_bundle.py` existe et est branché dans CI (`.github/workflows/validate.yml`). -- ⚠️ Le diff fonctionnel gameplay est scripté localement, mais pas encore exécuté automatiquement en CI. - -### 3) Documentation et guidage équipe -- ✅ `docs/WORKFLOWS.md` décrit l'enchaînement validation. -- ✅ Spec/arch conversation bundle documentent le périmètre. -- ⚠️ Il manque un guide "release gate" unique qui enchaîne G0→G5 en une seule checklist opérationnelle. - -### 4) Risques principaux -- Divergence entre bundle conversationnel et canon gameplay si la promotion n'est pas rituelle. -- G4/G5 encore partiels (pas de lot release/compliance structuré dans ce cycle). -- Dépendance implicite à PyYAML: bootstrap présent, mais doit rester obligatoire en CI locale/README. - -## Plan de progression recommandé (prochaines PRs) - -### PR-A (QA/Test) -- Ajouter un job CI dédié qui exécute aussi `tools/scenario/diff_gameplay_scenarios.py`. -- Publier l'artifact markdown de diff en sortie CI (upload artifact GitHub Actions). - -### PR-B (Doc/Release) -- Créer `docs/RELEASE_GATES.md` avec checklist G0→G5 exécutable. -- Standardiser le template de PR pour exiger la section "Gates satisfaites" + "limitations". - -### PR-C (Compliance) -- Ajouter un inventaire SBOM/licences minimal pour G5 (au moins dépendances Python validateurs + manifest repo). -- Centraliser les preuves dans `evidence/` avec index daté. - -## Décision proposée maintenant -- Garder le canon gameplay tel quel. -- Passer sur PR-A en priorité pour fermer l'écart CI sur le diff fonctionnel. diff --git a/docs/repo-status.md b/docs/repo-status.md deleted file mode 100644 index 4007977..0000000 --- a/docs/repo-status.md +++ /dev/null @@ -1,32 +0,0 @@ -# Repo Status Report - -## Tree summary -- `kit-maitre-du-jeu/`: full GM script, solutions, checklists, and export folders for PDF/PNG deliverables. -- `printables/`: `src/` plus `export/{pdf,png}/` for invitations, cards, badges, and the one-page rule set. -- `hardware/`: BOMs, wiring docs, and the ESP32/Arduino firmware flow with `.pio` dependencies (libraries such as Adafruit and ESP8266Audio naturally expand here). -- `docs/`: maintenance plan, repo audit, and asset folder (`docs/assets/` contains the repo map SVG already referenced in `README.md`). -- `examples/` and `scenario-ai-coherence/` (renamed for portability): auxiliary content and tools, now safe for Windows/CI. - -## Empty / TODO files -- `printables/invitations/export/pdf/.gitkeep` -- `printables/invitations/export/png/.gitkeep` -- `printables/invitations/src/.gitkeep` -- assorted `.nojekyll`, `.uno.test.skip`, and upstream library placeholders under `hardware/firmware/esp32/.pio/` (e.g., Adafruit BusIO examples); none appear to contain TODO hints, but they are empty files that may surprise contributors or scripts. - -## Broken links -- `hardware/firmware/esp32/.pio/libdeps/esp32dev/ESP8266Audio/README.md` line 250 → `examples/StreamMP3FromHTTP_SPIRAM/Schema_Spiram.png` (missing in that vendor snapshot). -- `hardware/firmware/esp32/.pio/libdeps/esp32_release/ESP8266Audio/README.md` line 250 → same missing `Schema_Spiram.png`. -- `hardware/firmware/esp32/.pio/libdeps/esp32dev/Mozzi/README.md` line 116 → `extras/NEWS.txt` (not included in the copied tree). -- `hardware/firmware/esp32/.pio/libdeps/esp32_release/Mozzi/README.md` line 116 → same missing `extras/NEWS.txt`. - -## Naming / portability issues -- `scenario-ai-coherence/` and `scenario-ai-coherence/version-finale/` now avoid spaces/colon, keeping the tree stable sur Windows/CI tooling. Continue to prefer hyphenated, lowercase identifiers pour tout nouveau sous-dossier. - -## Licences -- `README.md`, `CONTRIBUTING.md`, and `LICENSE.md` sont alignés autour du modèle CC BY-NC 4.0 pour les contenus créatifs et MIT pour le code (`LICENSES/CC-BY-NC-4.0.txt`, `LICENSES/MIT.txt`). Garder la même nomenclature accélère la revue des contributions. - -## Patch plan -- Continuer à surveiller les fichiers vides (`.gitkeep`, `.nojekyll`, `.uno.test.skip`) pour décider s’ils restent ou disparaissent lors d’un nettoyage futur. -- Corriger ou supprimer les liens cassés des bibliothèques `ESP8266Audio`/`Mozzi` si les assets nécessaires ne sont plus plastifiés. -- Valider chaque nouvelle version de scénario et de manifeste audio avec `tools/scenario/validate_scenario.py` et `tools/audio/validate_manifest.py`. -- Mettre à jour Quickstart, Styleguide et le workflow printables si la structure du kit ou les fichiers audio changent. diff --git a/docs/repo_state.json b/docs/repo_state.json deleted file mode 100644 index 73e3fe4..0000000 --- a/docs/repo_state.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "schema_version": "repo_state.v1", - "generated_at_utc": "2026-02-21T02:23:01Z", - "repo": "le-mystere-professeur-zacus", - "repo_url": "https://github.com/electron-rare/le-mystere-professeur-zacus.git", - "branch": "codex/repo-state-zacus", - "head": "3fec631c43c66a6ae12c12d2f652c8800d22b28f", - "head_date": "2026-02-21T02:59:01+01:00", - "head_subject": "feat(freenove): stabilize fallback AP outside local wifi (#103)", - "project_kind": "hardware_firmware_hybrid", - "pivot_changes": [ - { - "path": "hardware/firmware/data/story/apps/APP_WIFI.json", - "tags": [ - "firmware_build_test", - "hardware_validation" - ] - }, - { - "path": "hardware/firmware/docs/AGENT_TODO.md", - "tags": [ - "firmware_build_test", - "hardware_validation" - ] - }, - { - "path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/include/network_manager.h", - "tags": [ - "firmware_build_test", - "hardware_validation" - ] - }, - { - "path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/src/main.cpp", - "tags": [ - "firmware_build_test", - "hardware_validation" - ] - }, - { - "path": "hardware/firmware/hardware/firmware/ui_freenove_allinone/src/network_manager.cpp", - "tags": [ - "firmware_build_test", - "hardware_validation" - ] - } - ], - "impact_gates": [ - "firmware_build_test", - "hardware_validation" - ] -} diff --git a/docs/voice/VOICE_PIPELINE_GUIDE.md b/docs/voice/VOICE_PIPELINE_GUIDE.md new file mode 100644 index 0000000..dc0c565 --- /dev/null +++ b/docs/voice/VOICE_PIPELINE_GUIDE.md @@ -0,0 +1,299 @@ +# Voice Pipeline Guide — Professeur Zacus + +Technical guide for integrating voice interaction into the Zacus ESP32-S3 installation. + +## Overview + +The voice pipeline enables visitors to interact with Professeur Zacus using natural speech. The system uses a split architecture: wake word detection runs locally on the ESP32-S3 (via ESP-SR), while speech-to-text, LLM inference, and text-to-speech run server-side via the mascarade bridge. + +## Hardware Requirements + +### INMP441 MEMS Microphone + +| INMP441 Pin | ESP32-S3 GPIO | Notes | +|-------------|---------------|-------| +| SCK (BCLK) | GPIO 41 | I2S0 bit clock | +| WS (LRCLK) | GPIO 42 | I2S0 word select | +| SD (DATA) | GPIO 2 | I2S0 data in | +| L/R | GND | Left channel select | +| VDD | 3.3V | Power supply | +| GND | GND | Ground | + +**Important:** The microphone uses I2S0, which is a separate bus from the speaker output on I2S1 (MAX98357A). Both can operate simultaneously for full-duplex audio. + +### Speaker (already present) + +The Freenove All-in-One board includes a MAX98357A I2S amplifier on I2S1. No additional wiring needed for audio output. + +### Bill of Materials (voice-specific) + +| Part | Qty | Approx. Cost | Source | +|------|-----|-------------|--------| +| INMP441 breakout | 1 | 3-5 EUR | AliExpress, Amazon | +| Dupont wires (F-F) | 5 | included | — | + +## Software Requirements + +### ESP-IDF + esp-sr Component + +The ESP-SR library (WakeNet, MultiNet, AFE) requires the ESP-IDF framework. The current project uses Arduino framework via PlatformIO. + +**Option A: Full migration to ESP-IDF (recommended for production)** + +```ini +; platformio.ini changes +[env:freenove_allinone] +framework = espidf +; All Arduino-style code must be rewritten to ESP-IDF APIs +``` + +**Option B: Arduino as ESP-IDF component (gradual migration)** + +```ini +; platformio.ini changes +[env:freenove_allinone] +framework = arduino, espidf +; Allows mixing Arduino and ESP-IDF code +; esp-sr can be added as an IDF component +``` + +### esp-sr Component Registration + +Create or update `idf_component.yml`: +```yaml +dependencies: + espressif/esp-sr: + version: "^1.0.0" + # Includes: WakeNet, MultiNet, AFE (AEC + NS + VAD) +``` + +### Partition Table + +ESP-SR models need flash space. Update `partitions.csv`: +``` +# Name, Type, SubType, Offset, Size, Flags +model, data, spiffs, , 1500K, ; WakeNet + optional MultiNet models +``` + +## Voice Flow — Sequence Diagram + +```mermaid +sequenceDiagram + participant Mic as INMP441
(I2S0) + participant AFE as ESP-SR AFE
(AEC+NS+VAD) + participant WN as WakeNet9
("Prof Zacus") + participant WS as WebSocket
Client + participant Bridge as mascarade
bridge:4001 + participant LLM as LLM
(Qwen/GPT) + participant TTS as Piper TTS
(French) + participant Spk as Speaker
(I2S1) + + Note over Mic,AFE: LISTENING state + Mic->>AFE: 16kHz 16-bit PCM (continuous) + AFE->>WN: Cleaned audio frames + WN-->>WN: Scanning for wake word... + + Note over WN,WS: WAKE_DETECTED state + WN->>WS: Wake word detected! + Spk->>Spk: Play chime / LED feedback + + Note over WS,Bridge: STREAMING state + WS->>Bridge: {"type":"hello","wakeword":"professeur zacus"} + loop Until VAD silence + AFE->>WS: Processed audio frames + WS->>Bridge: OPUS-encoded binary frames + Bridge-->>WS: {"type":"stt","text":"..."} (interim) + end + WS->>Bridge: {"type":"end_of_speech"} + + Note over Bridge,LLM: PROCESSING state + Bridge->>LLM: Final transcription -> LLM prompt + LLM->>Bridge: Response text (streamed) + + Note over Bridge,Spk: SPEAKING state + Bridge->>TTS: Response text + TTS->>Bridge: OPUS audio stream + Bridge->>WS: {"type":"tts","state":"start"} + loop TTS audio chunks + Bridge->>WS: OPUS-encoded binary frames + WS->>Spk: Decode OPUS -> PCM -> I2S1 + end + Bridge->>WS: {"type":"tts","state":"end"} + + Note over Mic,Spk: Back to LISTENING +``` + +## XiaoZhi WebSocket Protocol + +The voice pipeline follows the [XiaoZhi-ESP32](https://github.com/78/xiaozhi-esp32) WebSocket protocol, which is well-documented and battle-tested. + +### Client -> Server Messages + +| Type | Format | Description | +|------|--------|-------------| +| Hello | `{"type":"hello","version":1,"wakeword":"..."}` | Session start after wake detection | +| Audio | Binary (OPUS frames) | 20ms OPUS-encoded audio at 16kHz mono | +| Abort | `{"type":"abort"}` | Cancel current interaction | +| End of speech | `{"type":"end_of_speech"}` | VAD detected end of utterance | + +### Server -> Client Messages + +| Type | Format | Description | +|------|--------|-------------| +| STT result | `{"type":"stt","text":"..."}` | Interim/final transcription | +| LLM text | `{"type":"llm","text":"...","emotion":"..."}` | Streamed LLM response | +| TTS control | `{"type":"tts","state":"start\|end"}` | TTS playback boundaries | +| TTS audio | Binary (OPUS frames) | OPUS-encoded speech audio | +| Emotion | `{"type":"emotion","value":"happy\|thinking\|..."}` | Facial expression hint | + +### Audio Encoding + +- **Codec:** OPUS (RFC 6716) +- **Frame size:** 20ms +- **Sample rate:** 16kHz +- **Channels:** Mono +- **Bitrate:** ~16kbps (voice-optimized) +- **Library:** `libopus` via ESP-IDF component or bundled in esp-sr + +## Server-Side Requirements + +### mascarade Bridge Endpoint + +The mascarade bridge (running on GrosMac or VM) needs a `/voice` WebSocket endpoint that: + +1. Receives OPUS audio frames from ESP32 +2. Decodes OPUS -> PCM +3. Runs STT (Whisper via faster-whisper or whisper.cpp) +4. Sends transcription to LLM pipeline (existing mascarade flow) +5. Sends LLM response text to TTS +6. Encodes TTS output as OPUS +7. Streams OPUS frames back to ESP32 + +This endpoint does not exist yet in mascarade. It will be added as a new route in the bridge service. + +### TTS Services + +Two TTS options are provided via Docker Compose (`tools/dev/docker-compose.tts.yml`): + +**Piper TTS (recommended for low latency)** +- CPU-only, runs on VM (192.168.0.119) +- French voice: `fr_FR-siwis-medium` or `fr_FR-upmc-medium` +- Latency: ~200ms for short sentences +- OpenAI-compatible API on port 8000 + +**Coqui XTTS-v2 (for voice cloning)** +- GPU required, deploy on KXKM-AI (RTX 4090) +- Can clone Professeur Zacus's voice from ~30s of sample audio +- Higher quality but higher latency (~1-2s) +- API on port 5002 + +### STT Service + +- **Recommended:** faster-whisper with `large-v3` model +- **Language:** French (`--language fr`) +- **Deploy on:** KXKM-AI for GPU inference, or VM for CPU (slower) +- **Alternative:** whisper.cpp for CPU-optimized inference + +## Custom Wake Word — Espressif Process + +To get a custom "Professeur Zacus" wake word model for WakeNet9: + +1. **Contact Espressif** via https://www.espressif.com/en/contact-us +2. **Provide:** + - Target phrase: "Professeur Zacus" + - Language: French + - 200+ audio recordings of the phrase (diverse speakers, environments) + - Target platform: ESP32-S3 with WakeNet9 +3. **Timeline:** 2-4 weeks +4. **Cost:** Varies (may be free for open-source/educational projects) +5. **Delivery:** `.wn9` model file to flash to partition + +### Recording Tips for Training Data + +- Record in the target environment (exhibition space) +- Include male/female voices, different ages +- Include background noise variations +- Record at 16kHz, 16-bit, mono WAV +- Minimum 200 recordings, ideally 500+ +- Use the INMP441 mic itself for best acoustic match + +## French Voice Command Strategy + +**Local MultiNet is NOT recommended for French** because: +- MultiNet only supports Chinese, English, and a few other languages +- French phonetics are complex (liaisons, nasals, silent letters) +- Limited command vocabulary even in supported languages + +**Strategy: Server-side ASR for all speech recognition** + +1. Wake word detection: Local (WakeNet9, language-agnostic acoustic model) +2. All speech-to-text: Server-side via Whisper (excellent French support) +3. Intent parsing: LLM-based via mascarade (natural language understanding) +4. No local MultiNet needed — everything after wake word goes to server + +This simplifies the ESP32 firmware (no MultiNet model in flash) and gives unlimited French vocabulary through the LLM. + +## Migration Path — Arduino to ESP-IDF + +### Phase 1: Scaffold (current) +- Voice pipeline header + stubs in Arduino framework +- No ESP-SR dependency +- Compiles and runs (functions return safe defaults) + +### Phase 2: Arduino-as-Component +- Switch PlatformIO to `framework = arduino, espidf` +- Add esp-sr as IDF component +- Implement I2S mic input and AFE initialization +- Test wake word detection with "Hi Lexin" placeholder +- Keep all existing Arduino code unchanged + +### Phase 3: WebSocket Streaming +- Add OPUS encoder (IDF component) +- Implement WebSocket client for mascarade bridge +- Stream audio on wake detection +- Receive and play TTS responses + +### Phase 4: Production +- Order custom "Professeur Zacus" wake word +- Full ESP-IDF migration (optional, for memory optimization) +- AEC tuning (echo cancellation between speaker and mic) +- LED/display feedback during voice states + +## Memory Budget + +| Component | RAM Type | Size | Notes | +|-----------|----------|------|-------| +| WakeNet9 | PSRAM | ~340 KB | Single wake word model | +| AFE (1ch) | PSRAM | ~1.1 MB | AEC + noise suppression + VAD | +| OPUS codec | Internal | ~50 KB | Encoder + decoder | +| Audio buffers | PSRAM | ~64 KB | Ring buffers for I2S + WebSocket | +| **Total voice** | **Mixed** | **~1.5 MB** | | +| LVGL + UI | PSRAM | ~2 MB | Existing allocation | +| Camera | PSRAM | ~500 KB | JPEG buffer | +| **System total** | **PSRAM** | **~4 MB** | Of 8 MB available | + +## File Structure + +``` +ESP32_ZACUS/ui_freenove_allinone/ +├── include/voice/ +│ └── voice_pipeline.h # Public API (this scaffold) +├── src/voice/ +│ └── voice_pipeline.cpp # Stub implementation +└── ... + +docs/voice/ +└── VOICE_PIPELINE_GUIDE.md # This document + +tools/dev/ +└── docker-compose.tts.yml # TTS services (Piper + XTTS-v2) +``` + +## References + +- [XiaoZhi-ESP32](https://github.com/78/xiaozhi-esp32) — Open-source voice assistant for ESP32-S3 +- [ESP-SR Programming Guide](https://docs.espressif.com/projects/esp-sr/en/latest/) — WakeNet, MultiNet, AFE +- [Piper TTS](https://github.com/rhasspy/piper) — Fast local TTS with French models +- [Coqui XTTS-v2](https://github.com/coqui-ai/TTS) — Voice cloning TTS +- [faster-whisper](https://github.com/guillaumekln/faster-whisper) — CTranslate2-based Whisper +- [OPUS codec](https://opus-codec.org/) — Low-latency audio codec diff --git a/fronted dev web UI/.env.local.example b/fronted dev web UI/.env.local.example deleted file mode 100644 index 7a12e70..0000000 --- a/fronted dev web UI/.env.local.example +++ /dev/null @@ -1,4 +0,0 @@ -VITE_ZACUS_STUDIO_AI_URL=http://127.0.0.1:8787/story_generate -VITE_API_BASE=http://192.168.0.91 -VITE_API_PROBE_PORTS=80,8080 -VITE_API_FLAVOR=auto diff --git a/fronted dev web UI/.github/copilot-instructions.md b/fronted dev web UI/.github/copilot-instructions.md deleted file mode 100644 index 2b993f9..0000000 --- a/fronted dev web UI/.github/copilot-instructions.md +++ /dev/null @@ -1,14 +0,0 @@ -- [x] Verify that the copilot-instructions.md file in the .github directory is created. -- [x] Clarify Project Requirements -- [x] Scaffold the Project -- [x] Customize the Project -- [x] Install Required Extensions -- [x] Compile the Project -- [x] Create and Run Task -- [x] Launch the Project -- [x] Ensure Documentation is Complete - -Notes: -- Project: Vite + React + TypeScript + Tailwind. -- Dev server: npm run dev -- Build: npm run build diff --git a/fronted dev web UI/.gitignore b/fronted dev web UI/.gitignore deleted file mode 100644 index c000364..0000000 --- a/fronted dev web UI/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -# Build artifacts -.pio -.platformio -build -.venv - -node_modules -dist -dist-ssr -.svelte-kit -playwright-report -test-results -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/fronted dev web UI/README.md b/fronted dev web UI/README.md deleted file mode 100644 index 51ec509..0000000 --- a/fronted dev web UI/README.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -# Zacus WebUI (Story V2) -// TODO NO DEV FINISH (need KILL_LIFE ?) - -Frontend Mission Control pour les devices Zacus. - ---- - -## 📝 Description - -Interface web de pilotage, design et diagnostic pour les firmwares Zacus (Story V2 et legacy). - ---- - -## 📦 Fonctionnalités principales - -- Sélecteur de scénario avancé (recherche, tri, mode legacy, CTA adaptés) -- Orchestrateur live (statut runtime, recovery, filtres, contrôles réseau) -- Story Designer nodal (Cytoscape.js, import/export YAML, édition guidée, auto-layout) -- UI harmonisée (glass modern, labels FR, accessibilité) - ---- - -## 🚀 Installation & démarrage rapide - -```bash -npm install -npm run dev -``` - -Accès local : http://localhost:5173 -Accès LAN : http://:5173 - -Preset ESP : -```bash -npm run dev:esp -``` - ---- - -## 🛠️ Usage - -Détection automatique du firmware connecté : -- `story_v2` : endpoints `/api/story/*` + WebSocket -- `freenove_legacy` : endpoints `/api/status`, `/api/scenario/*`, `/api/stream` (SSE) - -Diagnostics firmware : -- Version, OTA, reboot détectés automatiquement -- État affiché dans le panneau "Firmware" - -Variables d'environnement : -- `VITE_API_BASE` (ex: `http://192.168.0.91`) -- `VITE_API_PROBE_PORTS` (défaut: `80,8080`) -- `VITE_API_FLAVOR` (`auto|story_v2|freenove_legacy`, défaut `auto`) -- `VITE_API_ACCESS_MODE` (`hybrid|direct|proxy`, défaut `hybrid`) -- `VITE_ZACUS_STUDIO_AI_URL` (ex: `http://127.0.0.1:8787/story_generate`) pour activer la génération IA locale des scénarios et du manifeste imprimables. - -Intégration IA locale (Docker) : - -```bash -cd tools/dev/docker-studio-ai -cp .env.example .env -docker compose up -d --build -docker exec -it zacus-ollama ollama pull qwen2.5-coder:14b -``` - -Puis lancer le frontend avec `fronted dev web UI/.env.local` : - -```bash -VITE_ZACUS_STUDIO_AI_URL=http://127.0.0.1:8787/story_generate -``` - ---- - -## 🤝 Contribuer - -Les contributions sont bienvenues ! -Merci de lire [../../CONTRIBUTING.md](../../CONTRIBUTING.md) avant toute PR. - ---- - -## 🧑‍🎓 Licence - -- **Code** : MIT (`../../LICENSE`) - ---- - -## 👤 Contact - -Pour toute question ou suggestion, ouvre une issue GitHub ou contacte l’auteur principal : -- Clément SAILLANT — [github.com/electron-rare](https://github.com/electron-rare) - -- Preview local: `http://localhost:4173` -- Preview LAN: `http://:4173` - -Build + preview: - -```bash -npm run preview:esp:build -``` - -## Tests / gates frontend - -```bash -npm run lint -npm run build -npm run test:unit -npm run test:e2e -npm run test:e2e:live -``` - -- `test:unit`: validation parser/generation YAML Story Designer. -- `playwright @mock`: detection Story V2/legacy, UX et regressions principales. -- `playwright @live`: tests live sur `192.168.0.91` (actions mutantes autorisees). - -## Notes live - -- La suite `@live` execute des actions de controle (`unlock`, `next`, `wifi reconnect`, `espnow off/on`). -- Un finalizer force `espnow on` en fin de run. -- A lancer sur une fenetre de test dediee (pas pendant une partie active). diff --git a/fronted dev web UI/eslint.config.js b/fronted dev web UI/eslint.config.js deleted file mode 100644 index 1642b95..0000000 --- a/fronted dev web UI/eslint.config.js +++ /dev/null @@ -1,19 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' - -export default defineConfig([ - globalIgnores(['dist', '.svelte-kit', 'build', 'coverage', 'playwright-report', 'test-results']), - { - files: ['**/*.{ts,js}'], - extends: [js.configs.recommended, ...tseslint.configs.recommended], - languageOptions: { - ecmaVersion: 2022, - globals: { - ...globals.browser, - ...globals.node, - }, - }, - }, -]) diff --git a/fronted dev web UI/package-lock.json b/fronted dev web UI/package-lock.json deleted file mode 100644 index e4ac977..0000000 --- a/fronted dev web UI/package-lock.json +++ /dev/null @@ -1,4194 +0,0 @@ -{ - "name": "fronted-dev-web-ui", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fronted-dev-web-ui", - "version": "1.0.0", - "dependencies": { - "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/kit": "^2.10.2", - "cytoscape": "^3.30.2", - "cytoscape-dagre": "^2.5.0", - "cytoscape-edgehandles": "^4.0.1", - "dagre": "^0.8.5", - "svelte": "^5.0.0", - "yaml": "^2.8.2" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@playwright/test": "^1.58.2", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@tailwindcss/vite": "^4.1.18", - "@types/dagre": "^0.7.53", - "@types/node": "^24.10.1", - "eslint": "^9.39.1", - "globals": "^16.5.0", - "svelte-check": "^4.0.0", - "tailwindcss": "^4.1.18", - "typescript": "~5.9.3", - "typescript-eslint": "^8.48.0", - "vite": "^7.3.1", - "vitest": "^4.0.18" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "license": "MIT" - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", - "integrity": "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", - "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/adapter-node": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.5.4.tgz", - "integrity": "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ==", - "license": "MIT", - "dependencies": { - "@rollup/plugin-commonjs": "^29.0.0", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.0", - "rollup": "^4.59.0" - }, - "peerDependencies": { - "@sveltejs/kit": "^2.4.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.53.4", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.4.tgz", - "integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.6.3", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "set-cookie-parser": "^3.0.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", - "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", - "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", - "license": "MIT", - "dependencies": { - "obug": "^2.1.0" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", - "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.2.1", - "@tailwindcss/oxide": "4.2.1", - "tailwindcss": "4.2.1" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.31.1", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.31.1", - "lightningcss-darwin-arm64": "1.31.1", - "lightningcss-darwin-x64": "1.31.1", - "lightningcss-freebsd-x64": "1.31.1", - "lightningcss-linux-arm-gnueabihf": "1.31.1", - "lightningcss-linux-arm64-gnu": "1.31.1", - "lightningcss-linux-arm64-musl": "1.31.1", - "lightningcss-linux-x64-gnu": "1.31.1", - "lightningcss-linux-x64-musl": "1.31.1", - "lightningcss-win32-arm64-msvc": "1.31.1", - "lightningcss-win32-x64-msvc": "1.31.1" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, - "node_modules/@types/dagre": { - "version": "0.7.53", - "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz", - "integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", - "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/type-utils": "8.55.0", - "@typescript-eslint/utils": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.55.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", - "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", - "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.55.0", - "@typescript-eslint/types": "^8.55.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", - "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", - "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", - "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/utils": "8.55.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", - "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", - "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.55.0", - "@typescript-eslint/tsconfig-utils": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", - "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", - "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.55.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.18", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", - "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", - "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.18", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", - "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", - "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-dagre": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.5.0.tgz", - "integrity": "sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==", - "license": "MIT", - "dependencies": { - "dagre": "^0.8.5" - }, - "peerDependencies": { - "cytoscape": "^3.2.22" - } - }, - "node_modules/cytoscape-edgehandles": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cytoscape-edgehandles/-/cytoscape-edgehandles-4.0.1.tgz", - "integrity": "sha512-uSYshkqRZ4luCxK295bEVTg46q4ZW+fwJhcIzMrtfNR7zeAnJ38Z48kUGeu5ibtXkgLbcZAg0YE4ED2dRuaePg==", - "license": "MIT", - "dependencies": { - "lodash.memoize": "^4.1.2", - "lodash.throttle": "^4.1.1" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/dagre": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", - "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", - "license": "MIT", - "dependencies": { - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/devalue": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", - "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", - "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "license": "MIT" - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.58.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/set-cookie-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", - "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svelte": { - "version": "5.53.6", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz", - "integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "@types/trusted-types": "^2.0.7", - "acorn": "^8.12.1", - "aria-query": "5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.3", - "esm-env": "^1.2.1", - "esrap": "^2.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.4.tgz", - "integrity": "sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/svelte/node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/tailwindcss": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", - "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.55.0", - "@typescript-eslint/parser": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/utils": "8.55.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.0.18", - "@vitest/mocker": "4.0.18", - "@vitest/pretty-format": "4.0.18", - "@vitest/runner": "4.0.18", - "@vitest/snapshot": "4.0.18", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" - } - } -} diff --git a/fronted dev web UI/package.json b/fronted dev web UI/package.json deleted file mode 100644 index 5d3796d..0000000 --- a/fronted dev web UI/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "fronted-dev-web-ui", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "vite dev", - "dev:esp": "VITE_API_BASE=http://192.168.0.91 VITE_API_PROBE_PORTS=80,8080 VITE_API_FLAVOR=auto VITE_API_ACCESS_MODE=hybrid vite dev --host --port 5173", - "build": "svelte-kit sync && vite build", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "lint": "eslint . --ext .ts,.js", - "preview": "vite preview", - "preview:esp": "VITE_API_BASE=http://192.168.0.91 VITE_API_PROBE_PORTS=80,8080 VITE_API_FLAVOR=auto VITE_API_ACCESS_MODE=hybrid vite preview --host --port 4173", - "preview:esp:build": "npm run build && npm run preview:esp", - "test:unit": "vitest run", - "test:unit:watch": "vitest", - "test:e2e": "playwright test", - "test:e2e:live": "playwright test --grep @live" - }, - "dependencies": { - "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/kit": "^2.10.2", - "cytoscape": "^3.30.2", - "cytoscape-dagre": "^2.5.0", - "cytoscape-edgehandles": "^4.0.1", - "dagre": "^0.8.5", - "svelte": "^5.0.0", - "yaml": "^2.8.2" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@playwright/test": "^1.58.2", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@tailwindcss/vite": "^4.1.18", - "@types/dagre": "^0.7.53", - "@types/node": "^24.10.1", - "eslint": "^9.39.1", - "globals": "^16.5.0", - "tailwindcss": "^4.1.18", - "svelte-check": "^4.0.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.48.0", - "vite": "^7.3.1", - "vitest": "^4.0.18" - } -} diff --git a/fronted dev web UI/playwright.config.ts b/fronted dev web UI/playwright.config.ts deleted file mode 100644 index ad25353..0000000 --- a/fronted dev web UI/playwright.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' - -const isCi = !!process.env.CI -const hasCliGrep = process.argv.some((arg) => arg === '--grep' || arg.startsWith('--grep=')) - -export default defineConfig({ - testDir: './tests/e2e', - timeout: 45_000, - expect: { timeout: 10_000 }, - fullyParallel: true, - forbidOnly: isCi, - retries: isCi ? 1 : 0, - reporter: 'list', - grep: hasCliGrep ? undefined : /@mock/, - use: { - baseURL: 'http://127.0.0.1:4173', - headless: true, - trace: 'retain-on-failure', - }, - webServer: { - command: - 'VITE_API_BASE=http://127.0.0.1:4173 VITE_API_PROBE_PORTS=4173 VITE_API_FLAVOR=auto npm run dev -- --host 127.0.0.1 --port 4173', - url: 'http://127.0.0.1:4173', - reuseExistingServer: !isCi, - timeout: 120_000, - }, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], -}) diff --git a/fronted dev web UI/specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md b/fronted dev web UI/specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md deleted file mode 100644 index 07c0c80..0000000 --- a/fronted dev web UI/specs/STORY_DESIGNER_SCRATCH_LIKE_SPEC.md +++ /dev/null @@ -1,181 +0,0 @@ -# Spécification frontend — Story Designer (Svelte/Cytoscape, style Scratch-like) - -## 1) Objectif - -Spécifier le frontend web en alignement strict avec l’implémentation réelle : -- édition visuelle Story V2, -- pilotage runtime (déploiement, validation, test), -- pages support (dashboard, média, réseau, diagnostics). - -Le runtime reste la source de vérité côté exécution. -`hardware/firmware` est hors-scope frontend. - -## 2) Stack réelle (au lieu d’une stack cible théorique) - -Le projet web actuel est basé sur **SvelteKit + Svelte stores + Cytoscape**, et non React/XYFlow. - -Références implémentées : -- `fronted dev web UI/src/routes/+layout.svelte` -- `fronted dev web UI/src/routes/designer/+page.svelte` -- `fronted dev web UI/src/lib/stores/designer.store.ts` -- `fronted dev web UI/src/lib/stores/runtime.store.ts` -- `fronted dev web UI/src/lib/stores/scenario.store.ts` -- `fronted dev web UI/src/lib/stores/media.store.ts` -- `fronted dev web UI/src/lib/runtimeService.ts` -- `fronted dev web UI/src/lib/deviceApi.ts` -- `fronted dev web UI/src/features/story-designer/{types,storyYaml,validation}.ts` - -## 3) Décision UX : rester en style Scratch-like (skin), pas en Blockly - -**Décision actuelle:** -- On continue avec **Cytoscape comme moteur**, et un rendu visuel **Scratch-like par skin/UX** (palette de type bloc, badges de statut, panneaux propriétés dédiés, lien visuel orienté nœuds). -- Pas de bascule vers Blockly ou `@xyflow/react` tant que la conversion YAML↔graphe est stable et que la chaîne de build/test fonctionne. - -**Raison pragmatique:** -- La base technique existante est déjà opérationnelle et valide (`/api/story/validate`, `/api/story/deploy`, auto-layout, undo/redo, import/export). -- Refaire le moteur (Blockly/XYFlow) ferait sauter la logique métier actuelle : persistance, historique, et contrat `StoryGraphDocument`. -- La feuille de route “nœuds améliorés” est compatible avec le stack Cytoscape et permet d’évoluer visuellement sans refondre le moteur. - -**Plan recommandé (phase suivante):** -1. améliorer les nœuds existants (catégories visuelles + validité inline + mini-guide), -2. seulement ensuite évaluer un vrai Blockly si besoin produit par produit. - -## 4) Portée synchronisée - -- **Designer visuel** : édition d’un graphe, import YAML → graphe, export graphe → YAML. -- **Contrôle runtime** : validate, deploy, test-run, start/pause/resume/skip/unlock selon capacité. -- **Gestion média + réseau + diagnostics** déjà intégrés dans l’app SvelteKit. -- Définition du projet cohérente avec : - - `scenario-ai-coherence/zacus_conversation_bundle_v3/fsm_mermaid 2.md` - - `specs/STORY_RUNTIME_API_JSON_CONTRACT.md` - - `specs/MEDIA_MANAGER_RUNTIME_SPEC.md` - - `fronted dev web UI/specs/MEDIA_MANAGER_FRONTEND_SPEC.md` - -## 5) UX réelle du studio Designer - -Le routeur expose les onglets : -- Dashboard -- Scénario -- Designer -- Media Manager -- Réseau -- Diagnostics - -Sur la page `Designer`, le comportement réel attendu est : -- **Canvas Cytoscape** avec : - - création/suppression de node, - - déplacement libre (drag), - - liaison mode à deux clics (`Mode liaison` puis clic destination), - - auto-layout (`dagre`), - - styles visuels (initial, sélection, link mode). -- **Inspector latéral** : - - édition node (`step_id`, `screen_scene_id`, `audio_pack_id`, initial), - - édition edge (`event_type`, `event_name`, `priority`), - - suppression edge/node. -- **Validation locale** en continu (warnings/erreurs). -- **Actions globales** : - - Undo/Redo (stack max 80), - - import/export YAML, - - validate/deploy/test-run via runtime quand disponibles, - - statut de chargement/erreur moteur. -- **Persist local** : - - draft YAML `studio:v3:draft`, - - graphe JSON `studio:v3:graph` (localStorage). -- **Keyboard** : - - `Ctrl/Cmd+Z`, `Ctrl/Cmd+Y`. - -## 6) Modèle de données éditeur (source de vérité front) - -`StoryGraphDocument`: -- `scenarioId: string` -- `version: number` -- `initialStep: string` -- `appBindings[]` -- `nodes[]` : - - `id`, `stepId`, `screenSceneId`, `audioPackId`, `actions`, `apps`, `mp3GateOpen`, `x`, `y`, `isInitial` -- `edges[]` : - - `id`, `fromNodeId`, `toNodeId`, `trigger`, `eventType`, `eventName`, `afterMs`, `priority` - -## 7) Conversion YAML ↔ graph (tolérante) - -Le parser actuel supporte : -- `initial_step` ou `initial_step_id` (fallback sur premier step si invalide), -- `target_step_id` ou `target`, -- normalisation tokens par `normalizeToken` (majuscules + `_` + remplacement caractères non standards), -- `app_bindings` absent → bindings par défaut, -- import de `scenario` mal nommé → avertissements non bloquants, -- suppression des transitions avec target inexistant (warning + continue). - -La génération YAML : -- produit `id/version/initial_step/app_bindings/steps`, -- normalise les IDs, -- ordonne les nodes pour stabilité, -- `event_type` + `event_name` générés depuis `edge`. - -## 8) Validation - -Validation locale (`StoryGraphDocument`) : -- `scenarioId` non vide -- `version` valide -- au moins 1 node, exactement 1 initial -- `stepId` unique -- edges avec source/cible existantes -- `trigger`/`event_type` valides, `event_name` non vide, priorités bornées. - -Validation API : -- POST `/api/story/validate` via `scenarioStore.validateYaml`. -- Exécution bloquée en mode legacy (garde-fou via capabilities runtime). - -Déploiement/test : -- POST `/api/story/deploy` (`scenarioStore.deployYaml`) -- `test-run` = deploy → select → start (uniquement si story_v2). - -## 9) Intégration runtime et orchestrations - -Runtime store (`runtime.store.ts`) : -- bootstrap au chargement, -- polling toutes les ~3s, -- stream quand disponible (WS/SSE selon découverte), -- refresh global via `/api/status`, `/api/story/status`, `/api/story/list`, `/api/media/record/status`. - -Pilotage scénario : -- select/start/pause/resume/skip via `scenario.store.ts`, -- capacité runtime vérifiée (`canSelectScenario`, `canStart`, etc. de `deviceApi`). - -Média : -- `/api/media/files?kind=music|picture|recorder` -- `/api/media/play`, `/api/media/stop` -- `/api/media/record/start`, `/api/media/record/stop` -- fallback compatible via `/api/control` en cas d’échec dédié. - -Détection Media Manager : -- priorité `SCENE_MEDIA_MANAGER`, -- fallback `STEP_MEDIA_MANAGER`, -- fallback legacy si token `MEDIA_MANAGER` présent ailleurs. - -## 10) Mapping FSM runtime cible (DEFAULT) - -Le graphe/story édité doit couvrir le contrat du runtime source `fsm_mermaid 2.md` : -- `STEP_U_SON_PROTO` état initial, -- boucles, transitions, timeouts et actions/serial identiques en labels d’événements, -- conservation stricte des noms événementiels (`BTN`, `AUDIO_DONE`, `ACK_*`, `ETAPE*`, `FORCE_*`, `WIN_DUE`, etc.), -- tolérance des IDs mixtes (`STEP_*` + `SCENE_*`) sans blocage. - -## 11) Conditions d’acceptation front - -- 1) Designer chargé sans erreur de moteur Cytoscape. -- 2) Import YAML → export YAML en roundtrip sans perte fonctionnelle majeure. -- 3) Erreurs de validation locale visibles immédiatement (warnings/errors persistants dans le panneau statut). -- 4) Undo/Redo fonctionne avec historique cohérent (capacité 80). -- 5) Validate/deploy/test-run déclenchés uniquement si support API Story V2. -- 6) Auto-layout non destructif + persist local opérationnelle. -- 7) Détection Media Manager conforme (scene > step > legacy). - -## 12) Décision de livraison finale (frontend) - -- **On valide un “scratch-like visuel” basé sur `Cytoscape` + `nœuds améliorés`, pas une migration vers Blockly.** -- Les futurs gains UX passent par : - - amélioration de la peau visuelle des nœuds, - - éditoriaux contextuels plus guidants, - - validations inline plus fines, - - sans changer le moteur ou le contrat `StoryGraphDocument`. diff --git a/fronted dev web UI/svelte.config.js b/fronted dev web UI/svelte.config.js deleted file mode 100644 index 2898045..0000000 --- a/fronted dev web UI/svelte.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import adapter from '@sveltejs/adapter-node' -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - preprocess: vitePreprocess(), - kit: { - adapter: adapter(), - }, -} - -export default config diff --git a/fronted dev web UI/tsconfig.json b/fronted dev web UI/tsconfig.json deleted file mode 100644 index c4f0113..0000000 --- a/fronted dev web UI/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler", - "types": ["vite/client", "node"] - }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.d.ts", "src/**/*.svelte", "tests/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/fronted dev web UI/vite.config.ts b/fronted dev web UI/vite.config.ts deleted file mode 100644 index f94700e..0000000 --- a/fronted dev web UI/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite' -import tailwindcss from '@tailwindcss/vite' -import { defineConfig } from 'vite' - -export default defineConfig({ - plugins: [tailwindcss(), sveltekit()], - test: { - include: ['tests/unit/**/*.test.ts'], - environment: 'node', - passWithNoTests: false, - }, -}) diff --git a/fronted dev web UI/vitest.config.ts b/fronted dev web UI/vitest.config.ts deleted file mode 100644 index 88b5655..0000000 --- a/fronted dev web UI/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - include: ['tests/unit/**/*.test.ts'], - environment: 'node', - passWithNoTests: false, - }, -}) diff --git a/frontend-scratch-v2/README.md b/frontend-scratch-v2/README.md index 376e2b8..4342930 100644 --- a/frontend-scratch-v2/README.md +++ b/frontend-scratch-v2/README.md @@ -1,29 +1,9 @@ -# Zacus Scratch Frontend V2 (greenfield) +# Zacus Story Designer — React 19 + Blockly -Frontend "from scratch" pour designer un scenario Zacus avec des blocs type Scratch, -generer du YAML, puis appeler les endpoints Story V2. +Studio auteur visuel pour concevoir des scenarios Zacus avec des blocs type Scratch. +Genere du YAML canonique et communique avec le firmware ESP32 via l'API Story V2. -## Objectif - -- Ne pas reutiliser le frontend legacy du repo. -- Construire un socle neuf, orientee OSS/libre. -- Garder la compatibilite API avec `STORY_V2_WEBUI.md`. - -## Stack OSS retenue - -Validation rapide faite le 2026-03-01 (npm registry): - -| Brique | Version | Licence | Role | -| --- | --- | --- | --- | -| blockly | 12.4.1 | Apache-2.0 | editeur blocs | -| yaml | 2.8.2 | ISC | serialisation YAML | -| zod | 4.3.6 | MIT | validation locale de schema | -| ajv | 8.18.0 | MIT | JSON schema (next step) | -| @monaco-editor/react | 4.7.0 | MIT | vue YAML | - -Note: `scratch-gui` / `scratch-vm` sont AGPL-3.0-only, donc non retenus ici pour garder un front permissif. - -## Demarrage +## Setup ```bash cd frontend-scratch-v2 @@ -31,32 +11,66 @@ npm install npm run dev ``` -Option API: +Connexion API (optionnel) : ```bash VITE_STORY_API_BASE=http://:8080 npm run dev ``` -## Fonctionnalites livrees +## Scripts disponibles -- Workspace Blockly avec bloc `step`. -- Generation YAML live depuis les blocs. -- Preview/edit YAML dans Monaco. -- Actions runtime: - - GET `/api/story/list` - - GET `/api/story/status` - - POST `/api/story/validate` - - POST `/api/story/deploy` +| Commande | Description | +| --- | --- | +| `npm run dev` | Serveur de dev Vite (HMR) | +| `npm run build` | Build production (`dist/`) | +| `npm test` | Tests Vitest (18 tests) | +| `npm run lint` | ESLint | -## Structure +## Architecture -- `src/components/BlocklyDesigner.tsx`: editeur blocs + generation YAML. -- `src/components/RuntimeControls.tsx`: actions HTTP Story V2. -- `src/lib/scenario.ts`: mapping blocs -> document scenario -> YAML. -- `src/types.ts`: types du document scenario. +L'interface s'organise en 4 onglets : -## Prochain lot recommande +| Onglet | Composant | Role | +| --- | --- | --- | +| Designer | `BlocklyDesigner.tsx` | Editeur blocs + generation YAML live | +| Dashboard | `Dashboard.tsx` | Vue d'ensemble scenario | +| Media | `MediaManager.tsx` | Gestion assets audio/image | +| Network | `NetworkPanel.tsx` | Monitoring devices terrain | -1. Ajouter un mapping complet `steps + transitions` (FSM) avec validation croisee. -2. Ajouter import/export Blockly JSON. -3. Ajouter tests unitaires de generation YAML et smoke E2E API mock. +Autres fichiers cles : +- `src/components/RuntimeControls.tsx` — actions HTTP Story V2 (list, status, validate, deploy) +- `src/lib/scenario.ts` — mapping blocs → document scenario → YAML +- `src/types.ts` — types TypeScript du document scenario + +## Stack OSS + +| Brique | Version | Licence | Role | +| --- | --- | --- | --- | +| blockly | 12.4.1 | Apache-2.0 | editeur blocs | +| yaml | 2.8.2 | ISC | serialisation YAML | +| zod | 4.3.6 | MIT | validation locale | +| ajv | 8.18.0 | MIT | JSON schema | +| @monaco-editor/react | 4.7.0 | MIT | vue YAML | + +Note : `scratch-gui` / `scratch-vm` sont AGPL-3.0, non retenus pour garder un front permissif. + +## API Story V2 + +| Methode | Endpoint | Description | +| --- | --- | --- | +| GET | `/api/story/list` | Liste des scenarios | +| GET | `/api/story/status` | Statut runtime | +| POST | `/api/story/validate` | Validation YAML | +| POST | `/api/story/deploy` | Deploiement sur device | + +Variable d'environnement : `VITE_STORY_API_BASE` (defaut : pas de proxy). + +## Tests + +18 tests passing (Vitest). Lancer avec `npm test`. + +## Prochaines etapes + +1. Mapping complet steps + transitions (FSM) avec validation croisee +2. Import/export Blockly JSON +3. Tests E2E avec API mock diff --git a/frontend-scratch-v2/package-lock.json b/frontend-scratch-v2/package-lock.json index a3ca103..c85e9aa 100644 --- a/frontend-scratch-v2/package-lock.json +++ b/frontend-scratch-v2/package-lock.json @@ -28,7 +28,8 @@ "globals": "^16.5.0", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", - "vite": "^7.3.1" + "vite": "^7.3.1", + "vitest": "^3.2.4" } }, "node_modules/@asamuzakjp/css-color": { @@ -1592,6 +1593,24 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1960,6 +1979,121 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2031,6 +2165,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2108,6 +2252,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2139,6 +2293,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2156,6 +2327,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2261,6 +2442,16 @@ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2297,6 +2488,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", @@ -2560,6 +2758,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2570,6 +2778,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3028,6 +3246,13 @@ "dev": true, "license": "MIT" }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3038,6 +3263,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/marked": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", @@ -3215,6 +3450,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3441,6 +3693,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3451,12 +3710,26 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/state-local": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", "license": "MIT" }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3470,6 +3743,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3489,6 +3782,20 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3506,6 +3813,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -3735,6 +4072,102 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -3807,6 +4240,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/frontend-scratch-v2/package.json b/frontend-scratch-v2/package.json index 1a89bfb..0d93cf3 100644 --- a/frontend-scratch-v2/package.json +++ b/frontend-scratch-v2/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "test": "vitest run", "preview": "vite preview" }, "dependencies": { @@ -30,6 +31,7 @@ "globals": "^16.5.0", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", - "vite": "^7.3.1" + "vite": "^7.3.1", + "vitest": "^3.2.4" } } diff --git a/frontend-scratch-v2/src/App.css b/frontend-scratch-v2/src/App.css index c5ad849..3b7cd75 100644 --- a/frontend-scratch-v2/src/App.css +++ b/frontend-scratch-v2/src/App.css @@ -1,110 +1,453 @@ +/* ─── Reset & Base ─── */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --bg: #0f1117; + --bg-card: #1a1d27; + --bg-input: #252833; + --border: #2e3142; + --text: #e4e6eb; + --text-muted: #8b8fa3; + --accent: #3b82f6; + --accent-hover: #2563eb; + --green: #22c55e; + --red: #ef4444; + --orange: #f59e0b; + --radius: 8px; + --font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --mono: 'JetBrains Mono', 'Fira Code', monospace; +} + +body { + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 14px; + line-height: 1.5; +} + +/* ─── Shell ─── */ .app-shell { - min-height: 100vh; - padding: 1rem; display: flex; flex-direction: column; - gap: 1rem; + min-height: 100vh; +} + +/* ─── Header ─── */ +.app-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 20px; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + gap: 16px; + flex-wrap: wrap; } .app-header h1 { - margin: 0; - font-size: 1.6rem; + font-size: 18px; + font-weight: 700; + white-space: nowrap; } -.app-header p { - margin: 0.25rem 0 0; - opacity: 0.82; +.header-status { + display: flex; + align-items: center; + gap: 10px; } +.dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} +.dot.connected { + background: var(--green); + box-shadow: 0 0 6px var(--green); +} +.dot.disconnected { + background: var(--red); +} + +.api-url-input { + background: var(--bg-input); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius); + padding: 4px 10px; + font-size: 13px; + font-family: var(--mono); + width: 240px; +} + +.header-step { + color: var(--accent); + font-size: 13px; +} + +/* ─── Tabs ─── */ +.tab-bar { + display: flex; + gap: 0; + background: var(--bg-card); + border-bottom: 2px solid var(--border); + padding: 0 20px; +} + +.tab { + padding: 10px 20px; + background: none; + border: none; + color: var(--text-muted); + font-size: 14px; + font-weight: 500; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + transition: all 0.15s; + border-radius: 0; +} +.tab:hover { + color: var(--text); + background: none; + border-color: transparent; + border-bottom-color: var(--text-muted); +} +.tab.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +/* ─── Main ─── */ +.main-content { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +/* ─── Cards ─── */ +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; + margin-bottom: 16px; +} +.card h3 { + font-size: 14px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; +} + +.panel-stack { + display: flex; + flex-direction: column; + gap: 16px; +} + +.editor-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +/* ─── KV pairs ─── */ +.kv { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; + border-bottom: 1px solid var(--border); +} +.kv:last-child { + border-bottom: none; +} + +.runtime3-grid { + display: grid; + gap: 8px 16px; +} + +.runtime3-span { + grid-column: 1 / -1; +} + +.runtime3-path { + word-break: break-all; + text-align: right; +} + +/* ─── Badges ─── */ +.badge { + display: inline-block; + padding: 2px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + background: var(--bg-input); + color: var(--text-muted); +} +.badge.ok { + background: rgba(34, 197, 94, 0.15); + color: var(--green); +} +.badge.err { + background: rgba(239, 68, 68, 0.15); + color: var(--red); +} +.badge.active { + background: rgba(59, 130, 246, 0.15); + color: var(--accent); +} + +/* ─── Buttons ─── */ +button { + padding: 6px 14px; + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--bg-input); + color: var(--text); + font-size: 13px; + cursor: pointer; + transition: all 0.15s; +} +button:hover:not(:disabled) { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} +button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-top: 8px; +} + +/* ─── Inputs ─── */ +input[type='text'], +input[type='number'] { + background: var(--bg-input); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius); + padding: 6px 10px; + font-size: 13px; +} + +label { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 12px; + color: var(--text-muted); +} + +/* ─── Notices ─── */ +.notice { + padding: 8px 12px; + border-radius: var(--radius); + font-size: 13px; + margin: 8px 0; +} +.notice.warn { + background: rgba(245, 158, 11, 0.12); + color: var(--orange); + border: 1px solid rgba(245, 158, 11, 0.3); +} +.notice.info { + background: rgba(59, 130, 246, 0.12); + color: var(--accent); + border: 1px solid rgba(59, 130, 246, 0.3); +} +.notice.err { + background: rgba(239, 68, 68, 0.12); + color: var(--red); + border: 1px solid rgba(239, 68, 68, 0.3); +} + +/* ─── Progress ─── */ +.progress-bar { + display: flex; + align-items: center; + gap: 8px; + flex: 1; +} +.progress-bar .progress-fill { + height: 6px; + background: var(--accent); + border-radius: 3px; + transition: width 0.3s; + flex: 1; + max-width: 200px; +} +.progress-bar span { + font-size: 12px; + font-family: var(--mono); +} + +/* ─── Lists ─── */ +.scenario-list, +.file-list { + list-style: none; +} +.scenario-list li, +.file-list li { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 0; + border-bottom: 1px solid var(--border); +} +.scenario-list li:last-child, +.file-list li:last-child { + border-bottom: none; +} + +/* ─── Misc ─── */ +.mono { + font-family: var(--mono); + font-size: 13px; +} +.muted { + color: var(--text-muted); + font-size: 13px; +} +.feedback { + margin-top: 8px; + padding: 6px 10px; + border-radius: var(--radius); + background: rgba(59, 130, 246, 0.1); + color: var(--accent); + font-size: 13px; +} +.error-banner { + background: rgba(239, 68, 68, 0.12); + color: var(--red); + padding: 8px 12px; + border-radius: var(--radius); + margin-top: 12px; +} +.event-json { + font-family: var(--mono); + font-size: 12px; + color: var(--text-muted); + background: var(--bg); + padding: 8px; + border-radius: var(--radius); + overflow-x: auto; + max-height: 200px; +} + +/* ─── Designer grid ─── */ .app-grid { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 1rem; + grid-template-columns: 1fr 1fr; + gap: 16px; +} +@media (max-width: 900px) { + .app-grid { + grid-template-columns: 1fr; + } } .panel { - border: 1px solid #d0d7de; - border-radius: 0.75rem; - padding: 0.75rem; - background: #ffffff; - display: flex; - flex-direction: column; - gap: 0.75rem; - min-height: 300px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; } - .panel h2 { - margin: 0; - font-size: 1.05rem; + font-size: 15px; + font-weight: 600; + margin-bottom: 12px; } +/* ─── Blockly ─── */ .blockly-host { - height: 48vh; + height: 50vh; min-height: 340px; - border: 1px solid #d0d7de; - border-radius: 0.5rem; + border: 1px solid var(--border); + border-radius: var(--radius); overflow: hidden; } .toolbar { display: flex; + gap: 8px; + margin-bottom: 8px; flex-wrap: wrap; align-items: center; - gap: 0.5rem; -} - -.toolbar input { - min-width: 220px; - border: 1px solid #d0d7de; - border-radius: 0.4rem; - padding: 0.4rem 0.6rem; -} - -.toolbar button { - border: 1px solid #d0d7de; - border-radius: 0.4rem; - background: #f6f8fa; - padding: 0.4rem 0.7rem; } .status-line { - font-size: 0.85rem; - opacity: 0.9; + margin-top: 8px; + padding: 6px 10px; + border-radius: var(--radius); + background: rgba(34, 197, 94, 0.1); + color: var(--green); + font-size: 13px; } - .status-line.error { - color: #b42318; + background: rgba(239, 68, 68, 0.1); + color: var(--red); } +/* ─── Runtime panel ─── */ .runtime-panel { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.75rem; + margin-top: 12px; } - -.runtime-panel .actions { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; -} - .runtime-panel pre { - margin: 0; - min-height: 120px; - border: 1px solid #d0d7de; - border-radius: 0.5rem; - background: #f6f8fa; - padding: 0.6rem; - overflow: auto; - font-size: 0.8rem; + font-family: var(--mono); + font-size: 12px; + color: var(--text-muted); + background: var(--bg); + padding: 8px; + border-radius: var(--radius); + overflow-x: auto; + max-height: 200px; + margin-top: 8px; + white-space: pre-wrap; } -@media (max-width: 980px) { - .app-grid { - grid-template-columns: 1fr; - } - - .runtime-panel { - grid-template-columns: 1fr; - } +/* ─── Rec form ─── */ +.rec-form { + display: flex; + gap: 12px; + margin-bottom: 8px; +} +.rec-form input { + width: 120px; +} + +/* ─── Dashboard layout ─── */ +.dashboard { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 16px; +} +.dashboard h2 { + grid-column: 1 / -1; + font-size: 18px; + font-weight: 700; +} + +.media-manager, +.network-panel { + max-width: 720px; } diff --git a/frontend-scratch-v2/src/App.tsx b/frontend-scratch-v2/src/App.tsx index e14ed09..758fb72 100644 --- a/frontend-scratch-v2/src/App.tsx +++ b/frontend-scratch-v2/src/App.tsx @@ -1,40 +1,201 @@ -import { useState } from 'react'; -import Editor from '@monaco-editor/react'; -import { BlocklyDesigner } from './components/BlocklyDesigner'; +import { Component, Suspense, lazy, useState } from 'react'; +import type { ErrorInfo, ReactNode } from 'react'; import { RuntimeControls } from './components/RuntimeControls'; +import { Dashboard } from './components/Dashboard'; +import { useRuntimeStore } from './lib/useRuntimeStore'; +import { setApiBase, getApiBase } from './lib/api'; import './App.css'; +// ─── Lazy-loaded heavy components ─── + +const LazyBlocklyDesigner = lazy(() => + import('./components/BlocklyDesigner').then((m) => ({ default: m.BlocklyDesigner })), +); +const LazyMonacoEditor = lazy(() => import('@monaco-editor/react')); +const LazyMediaManager = lazy(() => + import('./components/MediaManager').then((m) => ({ default: m.MediaManager })), +); +const LazyNetworkPanel = lazy(() => + import('./components/NetworkPanel').then((m) => ({ default: m.NetworkPanel })), +); + +const LazyFallback = ( +
Loading editor...
+); + +// ─── ErrorBoundary ─── + +interface ErrorBoundaryProps { + children: ReactNode; + fallbackLabel?: string; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, info: ErrorInfo) { + console.error(`[ErrorBoundary${this.props.fallbackLabel ? ` ${this.props.fallbackLabel}` : ''}]`, error, info.componentStack); + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong{this.props.fallbackLabel ? ` in ${this.props.fallbackLabel}` : ''}.

+
{this.state.error?.message}
+ +
+ ); + } + return this.props.children; + } +} + +export { ErrorBoundary }; + +type Tab = 'dashboard' | 'designer' | 'media' | 'network'; + +const TABS: { id: Tab; label: string }[] = [ + { id: 'dashboard', label: 'Dashboard' }, + { id: 'designer', label: 'Designer' }, + { id: 'media', label: 'Media' }, + { id: 'network', label: 'Network' }, +]; + function App() { + const [tab, setTab] = useState('dashboard'); const [yaml, setYaml] = useState(''); + const [runtime3Json, setRuntime3Json] = useState(''); + const [apiUrl, setApiUrl] = useState(getApiBase()); + const runtime = useRuntimeStore(); + + const handleApiChange = (url: string) => { + setApiUrl(url); + setApiBase(url); + }; return (
-

Zacus Scratch Frontend V2

-

Nouvelle base from scratch, orientee Blockly + YAML + API Story V2.

+

Zacus Studio V2

+
+ + handleApiChange(e.target.value.trim())} + aria-label="ESP32 API URL" + placeholder="http://esp32-ip:8080" + /> + {runtime.story && ( + + {runtime.story.current_step} + + )} +
-
-
-

Designer blocs

- -
+ -
-

YAML runtime

- setYaml(value ?? '')} - options={{ - minimap: { enabled: false }, - scrollBeyondLastLine: false, - fontSize: 13, - }} - /> - -
+
+ {tab === 'dashboard' && ( + + + + )} + + {tab === 'designer' && ( + + +
+
+

Designer blocs

+ { + setYaml(draft.yaml); + setRuntime3Json(draft.runtime3Json); + }} + /> +
+
+
+

YAML canonique

+ +
+
+

IR Runtime 3

+ +
+ +
+
+
+
+ )} + + {tab === 'media' && ( + + + + + + )} + + {tab === 'network' && ( + + + + + + )}
); diff --git a/frontend-scratch-v2/src/__tests__/api.test.ts b/frontend-scratch-v2/src/__tests__/api.test.ts new file mode 100644 index 0000000..fc29116 --- /dev/null +++ b/frontend-scratch-v2/src/__tests__/api.test.ts @@ -0,0 +1,137 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// We need to test the internal jsonFetch + the exported helpers. +// Since jsonFetch is not exported, we test it indirectly through storyList / storyStatus +// and also test setApiBase / getApiBase directly. + +import { + setApiBase, + getApiBase, + storyList, + storyStatus, + ApiError, +} from '../lib/api'; + +// ─── Mock globalThis.fetch ─── + +const mockFetch = vi.fn<(...args: unknown[]) => Promise>(); + +beforeEach(() => { + vi.stubGlobal('fetch', mockFetch); + setApiBase('http://test-esp:8080'); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +// ─── Helpers ─── + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }); +} + +// ─── Tests ─── + +describe('setApiBase / getApiBase', () => { + it('stores and returns the base URL', () => { + setApiBase('http://192.168.0.42:8080'); + expect(getApiBase()).toBe('http://192.168.0.42:8080'); + }); + + it('strips trailing slashes', () => { + setApiBase('http://host:9000///'); + expect(getApiBase()).toBe('http://host:9000'); + }); +}); + +describe('jsonFetch (via storyList)', () => { + it('returns parsed JSON on 200', async () => { + const payload = { scenarios: [{ id: 'demo', version: 1, estimated_duration_s: 300 }] }; + mockFetch.mockResolvedValueOnce(jsonResponse(payload)); + + const result = await storyList(); + expect(result).toEqual(payload.scenarios); + expect(mockFetch).toHaveBeenCalledOnce(); + + const [url] = mockFetch.mock.calls[0] as [string]; + expect(url).toBe('http://test-esp:8080/api/story/list'); + }); + + it('throws ApiError on HTTP 500', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ error: 'internal failure' }, 500), + ); + + try { + await storyList(); + expect.unreachable('should have thrown'); + } catch (err) { + expect(err).toBeInstanceOf(ApiError); + expect((err as ApiError).status).toBe(500); + expect((err as ApiError).message).toBe('internal failure'); + } + }); + + it('throws on timeout (AbortError path)', async () => { + // Simulate a request that never resolves — the AbortController will fire + mockFetch.mockImplementation( + (...args: unknown[]) => + new Promise((_resolve, reject) => { + const init = args[1] as { signal?: AbortSignal } | undefined; + if (init?.signal) { + init.signal.addEventListener('abort', () => { + const err = new DOMException('The operation was aborted.', 'AbortError'); + reject(err); + }); + } + }), + ); + + // storyStatus uses the default 5000ms timeout — too long for tests. + // Instead we test indirectly: the AbortController fires, the promise rejects. + // We reduce the wait by calling storyStatus which has default timeout. + // For speed we rely on the abort signal being set up correctly. + const promise = storyStatus(); + await expect(promise).rejects.toThrow(/timed out/); + }, 10_000); +}); + +describe('storyList', () => { + it('calls /api/story/list and unwraps scenarios', async () => { + const scenarios = [ + { id: 'a', version: 2, estimated_duration_s: 600 }, + { id: 'b', version: 1, estimated_duration_s: 120 }, + ]; + mockFetch.mockResolvedValueOnce(jsonResponse({ scenarios })); + + const result = await storyList(); + expect(result).toHaveLength(2); + expect(result[0].id).toBe('a'); + }); +}); + +describe('storyStatus', () => { + it('calls /api/story/status and returns status object', async () => { + const status = { + status: 'running', + scenario_id: 'demo', + current_step: 'STEP_1', + progress_pct: 42, + started_at_ms: 1700000000000, + selected: 'demo', + queue_depth: 0, + }; + mockFetch.mockResolvedValueOnce(jsonResponse(status)); + + const result = await storyStatus(); + expect(result.status).toBe('running'); + expect(result.current_step).toBe('STEP_1'); + + const [url] = mockFetch.mock.calls[0] as [string]; + expect(url).toBe('http://test-esp:8080/api/story/status'); + }); +}); diff --git a/frontend-scratch-v2/src/__tests__/runtime3.test.ts b/frontend-scratch-v2/src/__tests__/runtime3.test.ts new file mode 100644 index 0000000..7117ae8 --- /dev/null +++ b/frontend-scratch-v2/src/__tests__/runtime3.test.ts @@ -0,0 +1,168 @@ +import { describe, it, expect } from 'vitest'; +import { + compileScenarioDocumentToRuntime3, + validateRuntime3Document, +} from '../lib/runtime3'; +import type { ScenarioDocument } from '../types'; + +// ─── Helpers ─── + +function makeDocument(overrides?: Partial): ScenarioDocument { + return { + id: 'test-scenario', + version: 1, + title: 'Test Scenario', + players: { min: 2, max: 6 }, + duration: { total_minutes: 45 }, + canon: { introduction: 'Intro', stakes: 'Stakes' }, + stations: [], + puzzles: [], + steps_narrative: [{ step_id: 'intro', scene: 'scene1', narrative: 'Start' }], + firmware: { + initial_step: 'intro', + steps: [ + { + step_id: 'intro', + screen_scene_id: 'scene_welcome', + audio_pack_id: 'pack_intro', + actions: ['play_music'], + apps: [], + transitions: [ + { + event_type: 'button', + event_name: 'btn_next', + target_step_id: 'puzzle_1', + priority: 10, + after_ms: 0, + }, + { + event_type: 'timer', + event_name: 'timeout', + target_step_id: 'puzzle_1', + priority: 5, + after_ms: 30000, + }, + ], + }, + { + step_id: 'puzzle_1', + screen_scene_id: 'scene_puzzle', + audio_pack_id: 'pack_puzzle', + actions: [], + apps: ['puzzle_app'], + transitions: [ + { + event_type: 'serial', + event_name: 'solved', + target_step_id: 'intro', + priority: 0, + after_ms: 0, + }, + ], + }, + ], + }, + ...overrides, + }; +} + +// ─── Tests ─── + +describe('compileScenarioDocumentToRuntime3', () => { + it('produces correct schema_version', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + expect(result.schema_version).toBe('zacus.runtime3.v1'); + }); + + it('sets metadata correctly', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + expect(result.metadata.generated_by).toBe('frontend-scratch-v2'); + expect(result.metadata.migration_mode).toBe('native'); + }); + + it('normalizes step IDs to uppercase', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + for (const step of result.steps) { + expect(step.id).toBe(step.id.toUpperCase()); + } + expect(result.steps[0].id).toBe('INTRO'); + expect(result.steps[1].id).toBe('PUZZLE_1'); + }); + + it('normalizes transition target_step_id to uppercase', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + for (const step of result.steps) { + for (const t of step.transitions) { + expect(t.target_step_id).toBe(t.target_step_id.toUpperCase()); + } + } + }); + + it('sorts transitions are preserved with priority values', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + const introTransitions = result.steps[0].transitions; + expect(introTransitions).toHaveLength(2); + // Priorities are preserved as-is from the source + expect(introTransitions[0].priority).toBe(10); + expect(introTransitions[1].priority).toBe(5); + }); + + it('sets entry_step_id correctly from firmware.initial_step', () => { + const result = compileScenarioDocumentToRuntime3(makeDocument()); + expect(result.scenario.entry_step_id).toBe('INTRO'); + }); + + it('falls back to first firmware step if initial_step is missing', () => { + const doc = makeDocument(); + delete doc.firmware!.initial_step; + const result = compileScenarioDocumentToRuntime3(doc); + expect(result.scenario.entry_step_id).toBe('INTRO'); + }); + + it('falls back to STEP_BOOT when no steps exist', () => { + const doc = makeDocument({ + firmware: { steps: [] }, + steps_narrative: [], + }); + const result = compileScenarioDocumentToRuntime3(doc); + expect(result.scenario.entry_step_id).toBe('STEP_BOOT'); + }); + + it('normalizes scenario id', () => { + const doc = makeDocument({ id: 'my cool scenario!' }); + const result = compileScenarioDocumentToRuntime3(doc); + expect(result.scenario.id).toBe('MY_COOL_SCENARIO'); + }); + + it('handles unknown event_type by falling back to serial', () => { + const doc = makeDocument(); + doc.firmware!.steps![0].transitions[0].event_type = 'unknown_event'; + const result = compileScenarioDocumentToRuntime3(doc); + expect(result.steps[0].transitions[0].event_type).toBe('serial'); + }); +}); + +describe('validateRuntime3Document', () => { + it('validates a correct document', () => { + const doc = compileScenarioDocumentToRuntime3(makeDocument()); + const result = validateRuntime3Document(doc); + expect(result).toEqual({ ok: true }); + }); + + it('rejects empty steps', () => { + const doc = compileScenarioDocumentToRuntime3( + makeDocument({ firmware: { steps: [] }, steps_narrative: [] }), + ); + doc.steps = []; + const result = validateRuntime3Document(doc); + expect(result).toEqual({ ok: false, error: 'runtime3 requires at least one step' }); + }); + + it('rejects duplicate step IDs', () => { + const doc = compileScenarioDocumentToRuntime3(makeDocument()); + doc.steps.push({ ...doc.steps[0] }); // duplicate + const result = validateRuntime3Document(doc); + expect(result.ok).toBe(false); + expect('error' in result && result.error).toContain('duplicate'); + }); +}); diff --git a/frontend-scratch-v2/src/__tests__/scenario.test.ts b/frontend-scratch-v2/src/__tests__/scenario.test.ts new file mode 100644 index 0000000..b770728 --- /dev/null +++ b/frontend-scratch-v2/src/__tests__/scenario.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect } from 'vitest'; +import { + buildScenarioFromBlocks, + validateScenarioDocument, + normalizeId, +} from '../lib/scenario'; +import type { ScenarioStep, ScenarioDocument } from '../types'; + +// ─── Helpers ─── + +function makeValidSteps(overrides?: Partial[]): ScenarioStep[] { + const defaults: ScenarioStep[] = [ + { + stepId: 'STEP_A', + sceneId: 'SCENE_A', + transitions: [ + { + eventType: 'button', + eventName: 'BTN_NEXT', + targetStepId: 'STEP_B', + priority: 0, + afterMs: 0, + }, + ], + }, + { + stepId: 'STEP_B', + sceneId: 'SCENE_B', + transitions: [], + }, + ]; + if (!overrides) return defaults; + return overrides.map((o, i) => ({ ...defaults[i % defaults.length], ...o })); +} + +function makeValidDocument( + steps?: ScenarioStep[], + scenarioId = 'TEST_SCENARIO', +): ScenarioDocument { + return buildScenarioFromBlocks(scenarioId, steps ?? makeValidSteps()); +} + +// ─── Tests ─── + +describe('scenario validation', () => { + it('valid scenario passes validation', () => { + const doc = makeValidDocument(); + const result = validateScenarioDocument(doc); + expect(result).toEqual({ ok: true }); + }); + + it('missing required field (empty id) fails', () => { + const doc = makeValidDocument(makeValidSteps(), ''); + // buildScenarioFromBlocks normalizes empty to fallback, so force it + doc.id = ''; + const result = validateScenarioDocument(doc); + expect(result.ok).toBe(false); + }); + + it('missing title fails', () => { + const doc = makeValidDocument(); + doc.title = ''; + const result = validateScenarioDocument(doc); + expect(result.ok).toBe(false); + }); + + it('duplicate step IDs detected', () => { + const doc = makeValidDocument(); + // Manually inject duplicate step IDs into firmware + if (doc.firmware?.steps) { + doc.firmware.steps.push({ ...doc.firmware.steps[0] }); + doc.firmware.steps_reference_order = doc.firmware.steps.map( + (s) => s.step_id, + ); + } + doc.steps_narrative.push({ ...doc.steps_narrative[0] }); + const result = validateScenarioDocument(doc); + expect(result.ok).toBe(false); + expect('error' in result && result.error).toContain('duplicate'); + }); + + it('transition target pointing to existing step passes', () => { + const steps: ScenarioStep[] = [ + { + stepId: 'STEP_1', + sceneId: 'SCENE_1', + transitions: [ + { + eventType: 'serial', + eventName: 'BTN_GO', + targetStepId: 'STEP_2', + priority: 0, + afterMs: 0, + }, + ], + }, + { stepId: 'STEP_2', sceneId: 'SCENE_2', transitions: [] }, + ]; + const doc = makeValidDocument(steps); + expect(validateScenarioDocument(doc)).toEqual({ ok: true }); + }); + + it('transition target pointing to non-existing step fails', () => { + const steps: ScenarioStep[] = [ + { + stepId: 'STEP_1', + sceneId: 'SCENE_1', + transitions: [ + { + eventType: 'serial', + eventName: 'BTN_GO', + targetStepId: 'STEP_GHOST', + priority: 0, + afterMs: 0, + }, + ], + }, + ]; + const doc = makeValidDocument(steps); + const result = validateScenarioDocument(doc); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain('STEP_GHOST'); + } + }); + + it('players.min > players.max fails', () => { + const doc = makeValidDocument(); + doc.players = { min: 20, max: 5 }; + const result = validateScenarioDocument(doc); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain('players'); + } + }); + + it('empty steps array produces fallback STEP_BOOT', () => { + const doc = makeValidDocument([]); + expect(doc.firmware?.steps).toHaveLength(1); + expect(doc.firmware?.steps?.[0].step_id).toBe('STEP_BOOT'); + expect(validateScenarioDocument(doc)).toEqual({ ok: true }); + }); +}); + +describe('normalizeId', () => { + it('empty string returns fallback', () => { + expect(normalizeId('', 'FALLBACK')).toBe('FALLBACK'); + }); + + it('whitespace-only returns fallback', () => { + expect(normalizeId(' ', 'FB')).toBe('FB'); + }); + + it('special characters are replaced with underscores', () => { + expect(normalizeId('hello-world!@#', 'X')).toBe('HELLO_WORLD'); + }); + + it('unicode characters are stripped', () => { + const result = normalizeId('etape_cafe', 'X'); + expect(result).toBe('ETAPE_CAFE'); + }); + + it('leading/trailing underscores are removed', () => { + expect(normalizeId('__test__', 'X')).toBe('TEST'); + }); + + it('already valid ID is uppercased', () => { + expect(normalizeId('step_one', 'X')).toBe('STEP_ONE'); + }); +}); diff --git a/frontend-scratch-v2/src/components/BlocklyDesigner.tsx b/frontend-scratch-v2/src/components/BlocklyDesigner.tsx index 795156a..12abe85 100644 --- a/frontend-scratch-v2/src/components/BlocklyDesigner.tsx +++ b/frontend-scratch-v2/src/components/BlocklyDesigner.tsx @@ -5,7 +5,13 @@ import { buildScenarioFromBlocks, scenarioToYaml, validateScenarioDocument, + parseYamlToSteps, } from '../lib/scenario'; +import { + compileScenarioDocumentToRuntime3, + runtime3ToJson, + validateRuntime3Document, +} from '../lib/runtime3'; import type { ScenarioStep } from '../types'; const TOOLBOX: Blockly.utils.toolbox.ToolboxInfo = { @@ -13,14 +19,15 @@ const TOOLBOX: Blockly.utils.toolbox.ToolboxInfo = { contents: [ { kind: 'category', - name: 'Scenario', + name: 'Steps', colour: '#3b82f6', - contents: [ - { - kind: 'block', - type: 'zacus_step', - }, - ], + contents: [{ kind: 'block', type: 'zacus_step' }], + }, + { + kind: 'category', + name: 'Transitions', + colour: '#22c55e', + contents: [{ kind: 'block', type: 'zacus_transition' }], }, ], }; @@ -28,29 +35,50 @@ const TOOLBOX: Blockly.utils.toolbox.ToolboxInfo = { let blocksRegistered = false; function ensureCustomBlocks(): void { - if (blocksRegistered) { - return; - } + if (blocksRegistered) return; Blockly.defineBlocksWithJsonArray([ { type: 'zacus_step', - message0: 'step %1 scene %2', + message0: 'Step %1 Scene %2 Audio %3', args0: [ - { - type: 'field_input', - name: 'STEP_ID', - text: 'STEP_NEW', - }, - { - type: 'field_input', - name: 'SCENE_ID', - text: 'SCENE_NEW', - }, + { type: 'field_input', name: 'STEP_ID', text: 'STEP_NEW' }, + { type: 'field_input', name: 'SCENE_ID', text: 'SCENE_NEW' }, + { type: 'field_input', name: 'AUDIO_PACK', text: '' }, ], + message1: '%1', + args1: [{ type: 'input_statement', name: 'TRANSITIONS' }], previousStatement: null, nextStatement: null, colour: 210, + tooltip: 'A scenario step with its screen scene and audio pack', + }, + { + type: 'zacus_transition', + message0: 'on %1 : %2 → %3 after %4 ms prio %5', + args0: [ + { + type: 'field_dropdown', + name: 'EVENT_TYPE', + options: [ + ['button', 'button'], + ['serial', 'serial'], + ['timer', 'timer'], + ['audio_done', 'audio_done'], + ['unlock', 'unlock'], + ['espnow', 'espnow'], + ['action', 'action'], + ], + }, + { type: 'field_input', name: 'EVENT_NAME', text: 'BTN_NEXT' }, + { type: 'field_input', name: 'TARGET', text: 'STEP_NEXT' }, + { type: 'field_number', name: 'AFTER_MS', value: 0, min: 0, precision: 1 }, + { type: 'field_number', name: 'PRIORITY', value: 0, min: 0, precision: 1 }, + ], + previousStatement: null, + nextStatement: null, + colour: 140, + tooltip: 'A transition triggered by an event', }, ]); @@ -61,18 +89,36 @@ function readScenarioSteps(workspace: Blockly.WorkspaceSvg): ScenarioStep[] { const steps: ScenarioStep[] = []; const seenBlocks = new Set(); + const readTransitions = (block: Blockly.Block): ScenarioStep['transitions'] => { + const transitions: NonNullable = []; + let transitionBlock = block.getInputTargetBlock('TRANSITIONS'); + while (transitionBlock) { + if (transitionBlock.type === 'zacus_transition') { + transitions.push({ + eventType: transitionBlock.getFieldValue('EVENT_TYPE') ?? 'serial', + eventName: transitionBlock.getFieldValue('EVENT_NAME') ?? 'BTN_NEXT', + targetStepId: transitionBlock.getFieldValue('TARGET') ?? 'STEP_NEXT', + afterMs: Number(transitionBlock.getFieldValue('AFTER_MS') ?? 0), + priority: Number(transitionBlock.getFieldValue('PRIORITY') ?? 0), + }); + } + transitionBlock = transitionBlock.getNextBlock(); + } + return transitions; + }; + const collectChain = (startBlock: Blockly.Block): void => { let cursor: Blockly.Block | null = startBlock; while (cursor) { - if (seenBlocks.has(cursor.id)) { - break; - } + if (seenBlocks.has(cursor.id)) break; seenBlocks.add(cursor.id); if (cursor.type === 'zacus_step') { steps.push({ stepId: cursor.getFieldValue('STEP_ID') ?? '', sceneId: cursor.getFieldValue('SCENE_ID') ?? '', + audioPack: cursor.getFieldValue('AUDIO_PACK') ?? '', + transitions: readTransitions(cursor), }); } cursor = cursor.getNextBlock(); @@ -80,72 +126,129 @@ function readScenarioSteps(workspace: Blockly.WorkspaceSvg): ScenarioStep[] { }; for (const block of workspace.getTopBlocks(true)) { - if (block.type === 'zacus_step') { - collectChain(block); - } + if (block.type === 'zacus_step') collectChain(block); } if (steps.length === 0) { for (const block of workspace.getAllBlocks(false)) { - if (block.type === 'zacus_step') { - collectChain(block); - } + if (block.type === 'zacus_step') collectChain(block); } } - return steps; + // Deduplicate step IDs — keep first occurrence, warn on duplicates + const seenStepIds = new Set(); + const deduped: ScenarioStep[] = []; + for (const step of steps) { + if (seenStepIds.has(step.stepId)) { + console.warn( + `[BlocklyDesigner] Duplicate step ID "${step.stepId}" detected — skipping duplicate.`, + ); + continue; + } + seenStepIds.add(step.stepId); + deduped.push(step); + } + + return deduped; } -function addStarterBlocks(workspace: Blockly.WorkspaceSvg): void { - const first = workspace.newBlock('zacus_step'); - first.setFieldValue('STEP_U_SON_PROTO', 'STEP_ID'); - first.setFieldValue('SCENE_U_SON_PROTO', 'SCENE_ID'); - first.initSvg(); - first.render(); - first.moveBy(40, 40); +function loadStepsIntoWorkspace( + workspace: Blockly.WorkspaceSvg, + steps: ScenarioStep[], +): void { + workspace.clear(); + let prevBlock: Blockly.Block | null = null; + const Y_START = 40; + const X_START = 40; - const second = workspace.newBlock('zacus_step'); - second.setFieldValue('STEP_LA_DETECTOR', 'STEP_ID'); - second.setFieldValue('SCENE_LA_DETECTOR', 'SCENE_ID'); - second.initSvg(); - second.render(); + for (const step of steps) { + const block = workspace.newBlock('zacus_step'); + block.setFieldValue(step.stepId || 'STEP_NEW', 'STEP_ID'); + block.setFieldValue(step.sceneId || 'SCENE_NEW', 'SCENE_ID'); + block.setFieldValue(step.audioPack || '', 'AUDIO_PACK'); + block.initSvg(); + block.render(); - if (first.nextConnection && second.previousConnection) { - first.nextConnection.connect(second.previousConnection); + const transitions = step.transitions ?? []; + let previousTransitionBlock: Blockly.Block | null = null; + for (const transition of transitions) { + const transitionBlock = workspace.newBlock('zacus_transition'); + transitionBlock.setFieldValue(transition.eventType, 'EVENT_TYPE'); + transitionBlock.setFieldValue(transition.eventName, 'EVENT_NAME'); + transitionBlock.setFieldValue(transition.targetStepId, 'TARGET'); + transitionBlock.setFieldValue(String(transition.afterMs), 'AFTER_MS'); + transitionBlock.setFieldValue(String(transition.priority), 'PRIORITY'); + transitionBlock.initSvg(); + transitionBlock.render(); + + if (!previousTransitionBlock) { + const inputConnection = block.getInput('TRANSITIONS')?.connection; + if (inputConnection && transitionBlock.previousConnection) { + inputConnection.connect(transitionBlock.previousConnection); + } + } else if ( + previousTransitionBlock.nextConnection && + transitionBlock.previousConnection + ) { + previousTransitionBlock.nextConnection.connect( + transitionBlock.previousConnection, + ); + } + + previousTransitionBlock = transitionBlock; + } + + if (prevBlock) { + if (prevBlock.nextConnection && block.previousConnection) { + prevBlock.nextConnection.connect(block.previousConnection); + } + } else { + block.moveBy(X_START, Y_START); + } + prevBlock = block; } } +function addStarterBlocks(workspace: Blockly.WorkspaceSvg): void { + loadStepsIntoWorkspace(workspace, [ + { + stepId: 'STEP_U_SON_PROTO', + sceneId: 'SCENE_U_SON_PROTO', + transitions: [ + { + eventType: 'button', + eventName: 'ANY', + targetStepId: 'STEP_LA_DETECTOR', + priority: 0, + afterMs: 0, + }, + ], + }, + { stepId: 'STEP_LA_DETECTOR', sceneId: 'SCENE_LA_DETECTOR', transitions: [] }, + ]); +} + type BlocklyDesignerProps = { - onYamlChange: (yaml: string) => void; + onDraftChange: (draft: { yaml: string; runtime3Json: string }) => void; }; -export function BlocklyDesigner({ onYamlChange }: BlocklyDesignerProps) { +export function BlocklyDesigner({ onDraftChange }: BlocklyDesignerProps) { const hostRef = useRef(null); const workspaceRef = useRef(null); const [scenarioId, setScenarioId] = useState('zacus_v2_new'); const [steps, setSteps] = useState([]); const [copyInfo, setCopyInfo] = useState(''); + const fileInputRef = useRef(null); useEffect(() => { - if (!hostRef.current) { - return; - } + if (!hostRef.current) return; ensureCustomBlocks(); const workspace = Blockly.inject(hostRef.current, { toolbox: TOOLBOX, trashcan: true, - grid: { - spacing: 20, - length: 3, - colour: '#d0d7de', - snap: true, - }, - zoom: { - controls: true, - wheel: true, - startScale: 0.95, - }, + grid: { spacing: 20, length: 3, colour: '#3a3f52', snap: true }, + zoom: { controls: true, wheel: true, startScale: 0.95 }, }); workspaceRef.current = workspace; addStarterBlocks(workspace); @@ -167,21 +270,25 @@ export function BlocklyDesigner({ onYamlChange }: BlocklyDesignerProps) { const generated = useMemo(() => { const scenarioDocument = buildScenarioFromBlocks(scenarioId, steps); + const runtime3Document = compileScenarioDocumentToRuntime3(scenarioDocument); return { + scenarioDocument, yaml: scenarioToYaml(scenarioDocument), validation: validateScenarioDocument(scenarioDocument), + runtime3Json: runtime3ToJson(runtime3Document), + runtime3Validation: validateRuntime3Document(runtime3Document), }; }, [scenarioId, steps]); useEffect(() => { - onYamlChange(generated.yaml); - }, [generated.yaml, onYamlChange]); + onDraftChange({ + yaml: generated.yaml, + runtime3Json: generated.runtime3Json, + }); + }, [generated.runtime3Json, generated.yaml, onDraftChange]); const handleReset = () => { - if (!workspaceRef.current) { - return; - } - workspaceRef.current.clear(); + if (!workspaceRef.current) return; addStarterBlocks(workspaceRef.current); setSteps(readScenarioSteps(workspaceRef.current)); }; @@ -195,6 +302,37 @@ export function BlocklyDesigner({ onYamlChange }: BlocklyDesignerProps) { } }; + const handleImportYaml = (yamlStr: string) => { + const result = parseYamlToSteps(yamlStr); + if ('error' in result) { + setCopyInfo(`Import error: ${result.error}`); + return; + } + setScenarioId(result.id); + if (workspaceRef.current) { + loadStepsIntoWorkspace(workspaceRef.current, result.steps); + setSteps(readScenarioSteps(workspaceRef.current)); + } + setCopyInfo(`Imported ${result.steps.length} steps from "${result.id}".`); + }; + + const handleFileImport = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result === 'string') { + handleImportYaml(reader.result); + } + }; + reader.readAsText(file); + e.target.value = ''; + }; + return ( <>
@@ -206,22 +344,45 @@ export function BlocklyDesigner({ onYamlChange }: BlocklyDesignerProps) { placeholder="scenario id" /> + +
- {generated.validation.ok - ? `Ready - ${steps.length} step(s) detected. ${copyInfo}` - : `Invalid draft: ${generated.validation.error}`} + {generated.validation.ok && generated.runtime3Validation.ok + ? `Ready — ${steps.length} step(s), ${ + steps.reduce( + (total, step) => total + (step.transitions?.length ?? 0), + 0, + ) + } transition(s). ${copyInfo}` + : `Invalid: ${ + !generated.validation.ok + ? generated.validation.error + : generated.runtime3Validation.ok + ? 'runtime3 validation error' + : generated.runtime3Validation.error + }`}
); diff --git a/frontend-scratch-v2/src/components/Dashboard.tsx b/frontend-scratch-v2/src/components/Dashboard.tsx new file mode 100644 index 0000000..a58c53e --- /dev/null +++ b/frontend-scratch-v2/src/components/Dashboard.tsx @@ -0,0 +1,534 @@ +import { useEffect, useRef, useState } from 'react'; +import { + type ScenarioListItem, + type VoiceStatus, + type GameAnalytics, + storyList, + storySelect, + storyStart, + storyPause, + storyResume, + storySkip, + askHint, + voiceStatus, + gameAnalytics, +} from '../lib/api'; +import type { RuntimeState } from '../lib/useRuntimeStore'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface ChatMessage { + role: 'user' | 'zacus'; + text: string; + timestamp: number; +} + +type Props = { runtime: RuntimeState; refresh: () => void }; + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +export function Dashboard({ runtime, refresh }: Props) { + const [scenarios, setScenarios] = useState([]); + const [busy, setBusy] = useState(false); + const [feedback, setFeedback] = useState(''); + const [voiceInput, setVoiceInput] = useState(''); + const [voice, setVoice] = useState(null); + + // Chat history + const [chatHistory, setChatHistory] = useState([]); + const [chatLoading, setChatLoading] = useState(false); + const chatEndRef = useRef(null); + + // Hint level + const [hintLevel, setHintLevel] = useState(1); + + // Analytics + const [analytics, setAnalytics] = useState(null); + const [analyticsError, setAnalyticsError] = useState(''); + + // ─── Initial load ─── + useEffect(() => { + storyList() + .then(setScenarios) + .catch(() => setScenarios([])); + voiceStatus() + .then(setVoice) + .catch(() => setVoice(null)); + }, [runtime.connected]); + + // ─── Auto-poll voice status every 5s when connected ─── + useEffect(() => { + if (!runtime.connected) return; + const id = setInterval(() => { + voiceStatus().then(setVoice).catch(() => {}); + }, 5000); + return () => clearInterval(id); + }, [runtime.connected]); + + // ─── Scroll chat to bottom ─── + useEffect(() => { + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [chatHistory, chatLoading]); + + // ─── Helpers ─── + const run = async (label: string, fn: () => Promise) => { + setBusy(true); + setFeedback(''); + try { + await fn(); + setFeedback(`${label} OK`); + refresh(); + } catch (err) { + setFeedback( + `${label} failed: ${err instanceof Error ? err.message : err}`, + ); + } finally { + setBusy(false); + } + }; + + const handleAskZacus = async () => { + const text = voiceInput.trim(); + if (!text) return; + + const userMsg: ChatMessage = { role: 'user', text, timestamp: Date.now() }; + setChatHistory((prev) => [...prev, userMsg]); + setVoiceInput(''); + setChatLoading(true); + + try { + const puzzleId = runtime.story?.current_step || 'general'; + const res = await askHint(puzzleId, text, hintLevel); + const zacusMsg: ChatMessage = { + role: 'zacus', + text: res.hint, + timestamp: Date.now(), + }; + setChatHistory((prev) => [...prev, zacusMsg]); + } catch (err) { + const errorMsg: ChatMessage = { + role: 'zacus', + text: `[Error] ${err instanceof Error ? err.message : String(err)}`, + timestamp: Date.now(), + }; + setChatHistory((prev) => [...prev, errorMsg]); + } finally { + setChatLoading(false); + } + }; + + const refreshAnalytics = () => { + setAnalyticsError(''); + gameAnalytics() + .then(setAnalytics) + .catch((err) => + setAnalyticsError(err instanceof Error ? err.message : String(err)), + ); + }; + + const formatDuration = (ms: number) => { + const s = Math.floor(ms / 1000); + const m = Math.floor(s / 60); + const sec = s % 60; + return `${m}m ${sec}s`; + }; + + const story = runtime.story; + const legacy = runtime.legacy; + const net = legacy?.network; + const runtime3 = legacy?.runtime3; + + return ( +
+

Dashboard

+ + {/* Connection */} +
+

Connection

+
+ Status + + {runtime.connected ? 'Connected' : 'Disconnected'} + +
+ {net && ( + <> +
+ Network + {net.state ?? '?'} +
+
+ IP + {net.ip ?? '?'} +
+ + )} +
+ + {/* Story status */} +
+

Story runtime

+ {story ? ( + <> +
+ Status + {story.status} +
+
+ Scenario + {story.scenario_id || '\u2014'} +
+
+ Step + {story.current_step || '\u2014'} +
+
+ Progress +
+
+ {story.progress_pct}% +
+
+
+ Queue + {story.queue_depth} +
+ + ) : ( +

No Story V2 status available.

+ )} +
+ +
+

Runtime 3 adapter

+ {runtime3 ? ( +
+
+ Contract + + {legacy?.story.runtime_contract ?? 'story_v2'} + +
+
+ Status + + {runtime3.loaded + ? 'Loaded' + : runtime3.discovered + ? 'Discovered' + : 'Missing'} + +
+
+ Scenario + {runtime3.scenario_id || '\u2014'} +
+
+ Version + {runtime3.scenario_version || 0} +
+
+ Entry step + {runtime3.entry_step_id || '\u2014'} +
+
+ Migration + {runtime3.migration_mode || '\u2014'} +
+
+ Steps + {runtime3.step_count} +
+
+ Transitions + {runtime3.transition_count} +
+
+ Source + {runtime3.source_kind || '\u2014'} +
+
+ Schema + {runtime3.schema_version || '\u2014'} +
+
+ Bundle + {runtime3.path || '\u2014'} +
+ {runtime3.error && ( +
+ Runtime 3 error: {runtime3.error} +
+ )} +
+ ) : ( +

No Runtime 3 adapter metadata available.

+ )} +
+ + {/* Scenarios */} +
+

Scenarios

+ {scenarios.length === 0 ? ( +

No scenarios found.

+ ) : ( +
    + {scenarios.map((s) => ( +
  • + {s.id} + v{s.version} + +
  • + ))} +
+ )} +
+ + {/* Controls */} +
+

Controls

+
+ + + + +
+ {feedback &&

{feedback}

} +
+ + {/* Voice — Ask Professor Zacus (enhanced chat) */} +
+

Ask Professor Zacus

+
+ Voice Bridge + + {voice?.connected ? 'Connected' : 'Disconnected'} + +
+ + {/* Hint level selector */} +
+ Hint level: + {[1, 2, 3].map((lvl) => ( + + ))} +
+ + {/* Chat history */} +
+ {chatHistory.length === 0 && !chatLoading && ( +

+ Ask Professor Zacus a question about the puzzle... +

+ )} + {chatHistory.map((msg, i) => ( +
+
+ {msg.role === 'zacus' && ( + + Prof. Zacus + + )} + {msg.text} +
+ + {new Date(msg.timestamp).toLocaleTimeString()} + +
+ ))} + {chatLoading && ( +
+ + Professor Zacus is thinking... +
+ )} +
+
+ + {/* Hint count */} + {analytics && ( +
+ Hints used this session: {analytics.total_hints} +
+ )} + + {/* Input */} +
+ setVoiceInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && voiceInput.trim() && !chatLoading) { + handleAskZacus(); + } + }} + disabled={chatLoading} + style={{ flex: 1 }} + /> + +
+
+ + {/* Analytics */} +
+

+ Analytics{' '} + +

+ {analyticsError &&

{analyticsError}

} + {analytics ? ( +
+
+ Session + {analytics.session_id} +
+
+ Duration + {formatDuration(analytics.duration_ms)} +
+
+ Puzzles solved + + {analytics.puzzles_solved} / {analytics.puzzles.length} + +
+
+ Hints used + {analytics.total_hints} +
+
+ Total attempts + {analytics.total_attempts} +
+
+ ) : ( +

No analytics loaded. Press Refresh to fetch.

+ )} +
+ + {/* Last WS event */} + {runtime.lastEvent && ( +
+

Last event

+
+            {JSON.stringify(runtime.lastEvent, null, 2)}
+          
+
+ )} + + {runtime.lastError && ( +

{runtime.lastError}

+ )} + + {/* Spinner keyframe (injected once) */} + +
+ ); +} diff --git a/frontend-scratch-v2/src/components/MediaManager.tsx b/frontend-scratch-v2/src/components/MediaManager.tsx new file mode 100644 index 0000000..0961a57 --- /dev/null +++ b/frontend-scratch-v2/src/components/MediaManager.tsx @@ -0,0 +1,284 @@ +import { useCallback, useEffect, useState } from 'react'; +import { + type MediaFileList, + type MediaStatus, + mediaFiles, + mediaPlay, + mediaStop, + mediaRecordStart, + mediaRecordStop, + legacyControl, +} from '../lib/api'; +import type { RuntimeState } from '../lib/useRuntimeStore'; +import { isMediaManagerActive } from '../lib/useRuntimeStore'; + +type Props = { runtime: RuntimeState }; + +type FileCategory = 'music' | 'picture' | 'recorder'; + +const CATEGORIES: FileCategory[] = ['music', 'picture', 'recorder']; + +export function MediaManager({ runtime }: Props) { + const [listings, setListings] = useState< + Record + >({ + music: null, + picture: null, + recorder: null, + }); + const [busy, setBusy] = useState(false); + const [feedback, setFeedback] = useState(''); + const [recSeconds, setRecSeconds] = useState(20); + const [recFilename, setRecFilename] = useState('take_1.wav'); + + const media: MediaStatus | undefined = runtime.legacy?.media; + const active = isMediaManagerActive(runtime.legacy); + + const loadFiles = useCallback(async () => { + const results = await Promise.allSettled( + CATEGORIES.map((kind) => mediaFiles(kind)), + ); + const next = { ...listings }; + CATEGORIES.forEach((kind, i) => { + const r = results[i]; + next[kind] = r.status === 'fulfilled' ? r.value : null; + }); + setListings(next); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (active) loadFiles(); + }, [active, loadFiles]); + + const run = async (label: string, fn: () => Promise) => { + setBusy(true); + setFeedback(''); + try { + await fn(); + setFeedback(`${label} OK`); + } catch (err) { + setFeedback( + `${label} failed: ${err instanceof Error ? err.message : err}`, + ); + } finally { + setBusy(false); + } + }; + + const handlePlay = (path: string) => + run('Play', async () => { + if (media?.playing) await mediaStop(); + await mediaPlay(path); + }); + + const handleStop = () => run('Stop', mediaStop); + + const handleRecordStart = () => + run('Record', () => mediaRecordStart(recSeconds, recFilename)); + + const handleRecordStop = () => run('RecStop', mediaRecordStop); + + return ( +
+

Media Manager

+ + {!active && ( +
+ Media Manager not active on device. Current step:{' '} + + {runtime.legacy?.story.screen ?? runtime.legacy?.story.step ?? '?'} + +
+ )} + + {/* Media status */} + {media && ( +
+

Status

+
+ Ready + + {String(media.ready)} + +
+
+ Playing + + {String(media.playing)} + +
+
+ Recording + + {String(media.recording)} + +
+ {media.recording && ( +
+ Elapsed + + {media.record_elapsed_seconds}s / {media.record_limit_seconds}s + +
+ )} + {media.record_simulated && ( +
+ Recording is simulated (placeholder WAV). +
+ )} + {media.last_error && ( +
+ Last error: {media.last_error} +
+ )} +
+ )} + + {/* File listings */} + {CATEGORIES.map((kind) => { + const list = listings[kind]; + return ( +
+

{kind.charAt(0).toUpperCase() + kind.slice(1)}

+ {!list ? ( +

+ Not loaded.{' '} + +

+ ) : list.files.length === 0 ? ( +

No files.

+ ) : ( +
    + {list.files.map((f) => ( +
  • + {f} + {kind === 'music' && ( + + )} +
  • + ))} +
+ )} +
+ ); + })} + + {/* Playback controls */} +
+

Playback

+
+ + +
+
+ + {/* Recording */} +
+

Recording

+
+ + +
+
+ + +
+
+ + {/* Boot mode */} +
+

Boot mode

+
+ + + + +
+
+ + {feedback &&

{feedback}

} +
+ ); +} diff --git a/frontend-scratch-v2/src/components/NetworkPanel.tsx b/frontend-scratch-v2/src/components/NetworkPanel.tsx new file mode 100644 index 0000000..eef3612 --- /dev/null +++ b/frontend-scratch-v2/src/components/NetworkPanel.tsx @@ -0,0 +1,75 @@ +import { useState } from 'react'; +import { networkReconnect, espnowOn, espnowOff } from '../lib/api'; +import type { RuntimeState } from '../lib/useRuntimeStore'; + +type Props = { runtime: RuntimeState }; + +export function NetworkPanel({ runtime }: Props) { + const [busy, setBusy] = useState(false); + const [feedback, setFeedback] = useState(''); + const net = runtime.legacy?.network; + + const run = async (label: string, fn: () => Promise) => { + setBusy(true); + setFeedback(''); + try { + await fn(); + setFeedback(`${label} OK`); + } catch (err) { + setFeedback( + `${label} failed: ${err instanceof Error ? err.message : err}`, + ); + } finally { + setBusy(false); + } + }; + + return ( +
+

Network

+ +
+

WiFi

+
+ State + {net?.state ?? '?'} +
+
+ IP + {net?.ip ?? '?'} +
+
+ +
+
+ +
+

ESP-NOW

+
+ + +
+
+ + {feedback &&

{feedback}

} +
+ ); +} diff --git a/frontend-scratch-v2/src/components/RuntimeControls.tsx b/frontend-scratch-v2/src/components/RuntimeControls.tsx index 0e191a1..20c2870 100644 --- a/frontend-scratch-v2/src/components/RuntimeControls.tsx +++ b/frontend-scratch-v2/src/components/RuntimeControls.tsx @@ -85,6 +85,20 @@ export function RuntimeControls({ yaml }: RuntimeControlsProps) { > Status + +