From d11e26cf2dba9e323e84d28a64f9738cc1dd1ce5 Mon Sep 17 00:00:00 2001 From: dillonj Date: Wed, 15 Apr 2026 16:36:21 -0600 Subject: [PATCH] improved tools for ai --- .diagnostics/diag_20260415_163239.tar.gz | Bin 0 -> 13648 bytes .../backend_health_check.txt | 16 + .../backend_python_version.txt | 3 + .../diag_20260415_163239/env_git_head.txt | 3 + .../diag_20260415_163239/env_git_status.txt | 10 + .../diag_20260415_163239/env_node_version.txt | 3 + .../diag_20260415_163239/env_npm_version.txt | 3 + .../diag_20260415_163239/env_uname.txt | 3 + .../diag_20260415_163239/frontend_build.txt | 11 + .../diag_20260415_163239/frontend_lint.txt | 3 + .../list_recent_files.txt | 72 + AI_dev_plan.md | 327 ++++ docs/ai-policy.md | 73 + docs/runbooks/error-codes.md | 113 ++ docs/spec-template.md | 113 ++ frontend/eslint.config.js | 26 + frontend/package-lock.json | 1412 +++++++++++++++++ frontend/package.json | 6 + frontend/src/App.tsx | 40 +- frontend/src/components/VolumePanel.tsx | 42 +- frontend/src/components/WaveformTimeline.tsx | 195 ++- frontend/src/store/editorStore.ts | 10 + frontend/tsconfig.tsbuildinfo | 2 +- scripts/collect-diagnostics.sh | 72 + scripts/validate-all.sh | 93 ++ 25 files changed, 2618 insertions(+), 33 deletions(-) create mode 100644 .diagnostics/diag_20260415_163239.tar.gz create mode 100644 .diagnostics/diag_20260415_163239/backend_health_check.txt create mode 100644 .diagnostics/diag_20260415_163239/backend_python_version.txt create mode 100644 .diagnostics/diag_20260415_163239/env_git_head.txt create mode 100644 .diagnostics/diag_20260415_163239/env_git_status.txt create mode 100644 .diagnostics/diag_20260415_163239/env_node_version.txt create mode 100644 .diagnostics/diag_20260415_163239/env_npm_version.txt create mode 100644 .diagnostics/diag_20260415_163239/env_uname.txt create mode 100644 .diagnostics/diag_20260415_163239/frontend_build.txt create mode 100644 .diagnostics/diag_20260415_163239/frontend_lint.txt create mode 100644 .diagnostics/diag_20260415_163239/list_recent_files.txt create mode 100644 AI_dev_plan.md create mode 100644 docs/ai-policy.md create mode 100644 docs/runbooks/error-codes.md create mode 100644 docs/spec-template.md create mode 100644 frontend/eslint.config.js create mode 100755 scripts/collect-diagnostics.sh create mode 100755 scripts/validate-all.sh diff --git a/.diagnostics/diag_20260415_163239.tar.gz b/.diagnostics/diag_20260415_163239.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..de035aa1ea50d28146f79e2528adef1ddc824917 GIT binary patch literal 13648 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", line 1, in + File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1387, in _gcd_import + File "", line 1360, in _find_and_load + File "", line 1331, in _find_and_load_unlocked + File "", line 935, in _load_unlocked + File "", line 999, in exec_module + File "", line 488, in _call_with_frames_removed + File "/home/dillon/_code/TalkEdit/backend/main.py", line 12, in + from routers import transcribe, export, ai, captions, audio +ModuleNotFoundError: No module named 'routers' diff --git a/.diagnostics/diag_20260415_163239/backend_python_version.txt b/.diagnostics/diag_20260415_163239/backend_python_version.txt new file mode 100644 index 0000000..66c1731 --- /dev/null +++ b/.diagnostics/diag_20260415_163239/backend_python_version.txt @@ -0,0 +1,3 @@ +# backend_python_version +# cmd: /home/dillon/_code/TalkEdit/.venv312/bin/python3.12 --version +Python 3.12.13 diff --git a/.diagnostics/diag_20260415_163239/env_git_head.txt b/.diagnostics/diag_20260415_163239/env_git_head.txt new file mode 100644 index 0000000..0ff894e --- /dev/null +++ b/.diagnostics/diag_20260415_163239/env_git_head.txt @@ -0,0 +1,3 @@ +# env_git_head +# cmd: git -C /home/dillon/_code/TalkEdit rev-parse --short HEAD +4f90750 diff --git a/.diagnostics/diag_20260415_163239/env_git_status.txt b/.diagnostics/diag_20260415_163239/env_git_status.txt new file mode 100644 index 0000000..0ffd057 --- /dev/null +++ b/.diagnostics/diag_20260415_163239/env_git_status.txt @@ -0,0 +1,10 @@ +# env_git_status +# cmd: git -C /home/dillon/_code/TalkEdit status --short + M frontend/src/App.tsx + M frontend/src/components/VolumePanel.tsx + M frontend/src/components/WaveformTimeline.tsx + M frontend/src/store/editorStore.ts +?? .diagnostics/ +?? AI_dev.md +?? docs/ +?? scripts/ diff --git a/.diagnostics/diag_20260415_163239/env_node_version.txt b/.diagnostics/diag_20260415_163239/env_node_version.txt new file mode 100644 index 0000000..e9add83 --- /dev/null +++ b/.diagnostics/diag_20260415_163239/env_node_version.txt @@ -0,0 +1,3 @@ +# env_node_version +# cmd: node --version +v22.18.0 diff --git a/.diagnostics/diag_20260415_163239/env_npm_version.txt b/.diagnostics/diag_20260415_163239/env_npm_version.txt new file mode 100644 index 0000000..da86151 --- /dev/null +++ b/.diagnostics/diag_20260415_163239/env_npm_version.txt @@ -0,0 +1,3 @@ +# env_npm_version +# cmd: npm --version +10.9.3 diff --git a/.diagnostics/diag_20260415_163239/env_uname.txt b/.diagnostics/diag_20260415_163239/env_uname.txt new file mode 100644 index 0000000..2f0428c --- /dev/null +++ b/.diagnostics/diag_20260415_163239/env_uname.txt @@ -0,0 +1,3 @@ +# env_uname +# cmd: uname -a +Linux cachyos-x 6.19.10-1-cachyos #1 SMP PREEMPT_DYNAMIC Wed, 25 Mar 2026 23:30:07 +0000 x86_64 GNU/Linux diff --git a/.diagnostics/diag_20260415_163239/frontend_build.txt b/.diagnostics/diag_20260415_163239/frontend_build.txt new file mode 100644 index 0000000..24c93fa --- /dev/null +++ b/.diagnostics/diag_20260415_163239/frontend_build.txt @@ -0,0 +1,11 @@ +# frontend_build +# cmd: bash -lc cd '/home/dillon/_code/TalkEdit/frontend' && npm run -s build +vite v6.4.1 building for production... +transforming... +✓ 1606 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 1.20 kB │ gzip: 0.57 kB +dist/assets/index-gyhcOzhr.css 19.31 kB │ gzip: 4.48 kB +dist/assets/index-B5NnH24A.js 354.13 kB │ gzip: 108.13 kB +✓ built in 2.43s diff --git a/.diagnostics/diag_20260415_163239/frontend_lint.txt b/.diagnostics/diag_20260415_163239/frontend_lint.txt new file mode 100644 index 0000000..e0ff36d --- /dev/null +++ b/.diagnostics/diag_20260415_163239/frontend_lint.txt @@ -0,0 +1,3 @@ +# frontend_lint +# cmd: bash -lc cd '/home/dillon/_code/TalkEdit/frontend' && npm run -s lint +sh: line 1: eslint: command not found diff --git a/.diagnostics/diag_20260415_163239/list_recent_files.txt b/.diagnostics/diag_20260415_163239/list_recent_files.txt new file mode 100644 index 0000000..f6b12fa --- /dev/null +++ b/.diagnostics/diag_20260415_163239/list_recent_files.txt @@ -0,0 +1,72 @@ +# list_recent_files +# cmd: find /home/dillon/_code/TalkEdit -maxdepth 2 -type f +/home/dillon/_code/TalkEdit/.git/description +/home/dillon/_code/TalkEdit/.git/packed-refs +/home/dillon/_code/TalkEdit/.git/COMMIT_EDITMSG +/home/dillon/_code/TalkEdit/.git/FETCH_HEAD +/home/dillon/_code/TalkEdit/.git/ORIG_HEAD +/home/dillon/_code/TalkEdit/.git/REBASE_HEAD +/home/dillon/_code/TalkEdit/.git/HEAD +/home/dillon/_code/TalkEdit/.git/config +/home/dillon/_code/TalkEdit/.git/index +/home/dillon/_code/TalkEdit/backend/requirements.txt +/home/dillon/_code/TalkEdit/backend/.python-version +/home/dillon/_code/TalkEdit/backend/dev_main.py +/home/dillon/_code/TalkEdit/backend/video_editor.py +/home/dillon/_code/TalkEdit/backend/audio_cleaner.py +/home/dillon/_code/TalkEdit/backend/diarization.py +/home/dillon/_code/TalkEdit/backend/ai_provider.py +/home/dillon/_code/TalkEdit/backend/caption_generator.py +/home/dillon/_code/TalkEdit/backend/background_removal.py +/home/dillon/_code/TalkEdit/backend/main.py +/home/dillon/_code/TalkEdit/frontend/postcss.config.js +/home/dillon/_code/TalkEdit/frontend/tailwind.config.js +/home/dillon/_code/TalkEdit/frontend/tsconfig.json +/home/dillon/_code/TalkEdit/frontend/vite.config.ts +/home/dillon/_code/TalkEdit/frontend/frontend_dev.log +/home/dillon/_code/TalkEdit/frontend/index.html +/home/dillon/_code/TalkEdit/frontend/package-lock.json +/home/dillon/_code/TalkEdit/frontend/package.json +/home/dillon/_code/TalkEdit/frontend/tsconfig.tsbuildinfo +/home/dillon/_code/TalkEdit/shared/project-schema.json +/home/dillon/_code/TalkEdit/node_modules/.package-lock.json +/home/dillon/_code/TalkEdit/src-tauri/.gitignore +/home/dillon/_code/TalkEdit/src-tauri/Cargo.toml +/home/dillon/_code/TalkEdit/src-tauri/build.rs +/home/dillon/_code/TalkEdit/src-tauri/tauri_dev.log +/home/dillon/_code/TalkEdit/src-tauri/Cargo.lock +/home/dillon/_code/TalkEdit/src-tauri/tauri.conf.json +/home/dillon/_code/TalkEdit/.dockerignore +/home/dillon/_code/TalkEdit/.gitattributes +/home/dillon/_code/TalkEdit/FIX-GITHUB-ACTIONS.md +/home/dillon/_code/TalkEdit/LICENSE +/home/dillon/_code/TalkEdit/M4A-SUPPORT.md +/home/dillon/_code/TalkEdit/package-lock.json +/home/dillon/_code/TalkEdit/TECH_FEATURES.md +/home/dillon/_code/TalkEdit/FFmpeg_COMPLIANCE.md +/home/dillon/_code/TalkEdit/transcribe.py +/home/dillon/_code/TalkEdit/test_api.py +/home/dillon/_code/TalkEdit/.vscode/settings.json +/home/dillon/_code/TalkEdit/.venv312/pyvenv.cfg +/home/dillon/_code/TalkEdit/webview.log +/home/dillon/_code/TalkEdit/.gitmodules +/home/dillon/_code/TalkEdit/split_audio.sh +/home/dillon/_code/TalkEdit/venv/.gitignore +/home/dillon/_code/TalkEdit/venv/pyvenv.cfg +/home/dillon/_code/TalkEdit/.gitignore +/home/dillon/_code/TalkEdit/FEATURES.md +/home/dillon/_code/TalkEdit/README.md +/home/dillon/_code/TalkEdit/close +/home/dillon/_code/TalkEdit/electron/main.js +/home/dillon/_code/TalkEdit/electron/preload.js +/home/dillon/_code/TalkEdit/electron/python-bridge.js +/home/dillon/_code/TalkEdit/idea summary.md +/home/dillon/_code/TalkEdit/open +/home/dillon/_code/TalkEdit/package.json +/home/dillon/_code/TalkEdit/plan.md +/home/dillon/_code/TalkEdit/.github/copilot-instructions.md +/home/dillon/_code/TalkEdit/AI_dev.md +/home/dillon/_code/TalkEdit/docs/spec-template.md +/home/dillon/_code/TalkEdit/docs/ai-policy.md +/home/dillon/_code/TalkEdit/scripts/validate-all.sh +/home/dillon/_code/TalkEdit/scripts/collect-diagnostics.sh diff --git a/AI_dev_plan.md b/AI_dev_plan.md new file mode 100644 index 0000000..9e128e7 --- /dev/null +++ b/AI_dev_plan.md @@ -0,0 +1,327 @@ +# AI Dev Roadmap + +## Purpose + +This document defines how TalkEdit can evolve toward highly autonomous AI-driven implementation and debugging. + +Goal: AI can execute most engineering work end-to-end with minimal human feedback while preserving safety, quality, and product intent. + +## Scope + +- Frontend: React + TypeScript + Vite +- Desktop host: Tauri +- Backend: FastAPI + Python services +- Media pipeline: FFmpeg, transcription, audio processing + +## Autonomy Target + +- Near-term target: 80-90% autonomous execution for well-scoped work. +- Mid-term target: 90-95% for low/medium-risk features with CI gates. +- 100% no-feedback autonomy is not realistic for ambiguous product decisions, legal/security tradeoffs, or high-risk migrations. + +## Core Principles + +1. Specs are executable and machine-readable. +2. Tests are the primary source of truth for completion. +3. Every failure is diagnosable from logs/artifacts. +4. AI has bounded permissions and policy guardrails. +5. AI updates docs and memory as part of done criteria. + +## Execution Status (2026-04-15) + +### Completed + +1. Added roadmap companion docs: + - `docs/spec-template.md` + - `docs/ai-policy.md` + - `docs/runbooks/error-codes.md` +2. Added operational scripts: + - `scripts/validate-all.sh` + - `scripts/collect-diagnostics.sh` +3. Ran Step 1 validation script (`./scripts/validate-all.sh`). +4. Ran Step 2 diagnostics script (`./scripts/collect-diagnostics.sh`). +5. Captured diagnostics archive: + - `.diagnostics/diag_20260415_163239.tar.gz` +6. Renamed roadmap file to `AI_dev_plan.md`. + +### Current Blockers + +1. Frontend lint baseline is not green yet. +2. Remaining lint issues are mostly pre-existing unused vars and hook dependency warnings across app components. + +### Next Actions + +1. Triage existing lint findings into: + - safe autofix + - manual low-risk cleanup + - intentional warnings to suppress with justification +2. Reach green `./scripts/validate-all.sh` in local dev. +3. Add CI workflow to enforce `validate-all` on pull requests. + +## Roadmap Phases + +## Phase 0: Foundation (1-2 weeks) + +### Deliverables + +1. Deterministic dev and test environment. +2. Baseline lint/type/test commands working in CI and local. +3. Standardized log format across frontend, backend, and Tauri host. + +### Tasks + +1. Stabilize toolchain commands: + - frontend lint/typecheck/test + - backend lint/typecheck/test + - workspace e2e smoke command +2. Add a single script for local validation, for example `npm run validate:all`. +3. Introduce structured logging fields: + - timestamp + - request/job id + - subsystem (frontend/backend/host) + - error code +4. Add reproducible media fixtures for tests under a dedicated test-fixtures path. + +### Exit Criteria + +- Fresh clone can run validation with one command. +- CI produces deterministic pass/fail on clean branches. +- Failures include enough context to reproduce without manual guessing. + +## Phase 1: Spec + Test Contracts (2-4 weeks) + +### Deliverables + +1. Feature spec template used for all new work. +2. API and schema contracts versioned and validated. +3. Regression harness for previous bugs. + +### Tasks + +1. Create `docs/spec-template.md` with required sections: + - user story + - acceptance criteria + - non-goals + - edge cases + - rollback behavior +2. Add contract tests for backend routers: + - transcribe + - export + - captions + - audio +3. Add project schema validation tests for `shared/project-schema.json` and project load/save behavior. +4. For each resolved bug, add a regression test before closing issue. + +### Exit Criteria + +- New feature PRs must include spec and tests. +- Breaking contract changes are detected automatically in CI. + +## Phase 2: Observability and Self-Debugging (2-3 weeks) + +### Deliverables + +1. Unified diagnostics bundle command. +2. AI-readable failure artifacts from CI and local runs. +3. Error taxonomy and runbook mapping. + +### Tasks + +1. Implement diagnostics command to collect: + - frontend logs + - backend logs + - Tauri logs + - failing test outputs + - environment metadata +2. Define error codes for common classes: + - media decode + - FFmpeg pipeline + - transcription model + - project load/save + - network/IPC bridge +3. Add runbook table mapping error codes to probable causes and first fixes. + +### Exit Criteria + +- Agent can identify likely root cause from artifacts without asking for manual logs. +- 80%+ of recurring failures map to known error classes. + +## Phase 3: Controlled Autonomous Implementation (3-5 weeks) + +### Deliverables + +1. Policy file defining what AI can edit/run without approval. +2. Autonomous task loop for implement -> validate -> fix -> revalidate. +3. Automatic PR summary with risk and assumptions. + +### Tasks + +1. Add policy file (for example `docs/ai-policy.md`): + - allowed directories for autonomous edits + - blocked files requiring approval + - blocked commands +2. Add task template for AI execution: + - parse feature spec + - locate impacted modules + - implement smallest changes + - run validation suite + - retry up to N fix cycles + - produce summary + residual risks +3. Require AI to update: + - copilot instructions + - changelog/roadmap note + - regression tests when bugfixing + +### Exit Criteria + +- Low-risk feature tasks complete end-to-end without human intervention. +- CI gate pass rate for autonomous PRs remains above agreed threshold (for example 95%). + +## Phase 4: High-Autonomy with Human Escalation (ongoing) + +### Deliverables + +1. Explicit escalation triggers for ambiguity and risk. +2. Broader autonomous scope with mandatory gates. +3. Drift monitoring for quality, velocity, and regressions. + +### Tasks + +1. Define escalation triggers: + - user-visible behavior changes without clear spec + - API/schema breakage + - security-sensitive modifications + - destructive migrations +2. Add quality dashboards: + - flaky tests + - escaped defects + - mean time to recovery + - autonomous task success rate +3. Monthly calibration: + - adjust autonomy scope + - update policies + - prune stale runbooks and memories + +### Exit Criteria + +- Autonomous throughput increases while defect rate stays stable or improves. +- Human review focuses on strategy and product decisions, not routine implementation/debugging. + +## Required Engineering Systems + +## 1. Spec System + +Minimum implementation: + +1. `docs/spec-template.md` +2. `docs/specs/` folder with one file per feature +3. CI check that new feature PRs include a spec reference + +## 2. Test System + +Minimum implementation: + +1. Frontend unit tests for stores/components/hook logic. +2. Backend unit+integration tests for routers/services. +3. E2E smoke tests for core workflow: + - open media + - transcribe + - edit zones + - export +4. Regression tests required for every bugfix. + +## 3. Environment System + +Minimum implementation: + +1. Locked dependencies and pinned runtimes. +2. Single bootstrap script. +3. Fixture media files for deterministic test runs. + +## 4. Observability System + +Minimum implementation: + +1. Structured logs. +2. Standard error codes. +3. Diagnostics bundle command. +4. CI artifact retention for failed runs. + +## 5. Governance System + +Minimum implementation: + +1. Protected branch + required checks. +2. Secret and dependency scanning. +3. Policy-based approval requirements for high-risk changes. + +## Suggested Repository Additions + +1. `AI_dev_plan.md` (this file) +2. `docs/spec-template.md` +3. `docs/ai-policy.md` +4. `docs/runbooks/error-codes.md` +5. `docs/runbooks/debug-playbooks.md` +6. `scripts/validate-all.sh` +7. `scripts/collect-diagnostics.sh` + +## Definition of Done for Autonomous Tasks + +A task is complete only if all items pass: + +1. Feature spec acceptance criteria satisfied. +2. Relevant tests added/updated and passing. +3. No lint/type errors in changed scope. +4. Docs and instructions updated if behavior changed. +5. Risk summary and assumptions recorded. + +## Escalation Rules (Must Ask Human) + +AI must stop and ask when: + +1. Requirement ambiguity changes user-visible behavior. +2. Multiple valid product decisions exist without clear preference. +3. Security/privacy/compliance implications are uncertain. +4. Data loss or destructive migration is possible. +5. CI remains failing after bounded auto-fix attempts. + +## Metrics to Track + +1. Autonomous task success rate. +2. Reopen rate of AI-completed tasks. +3. Regression rate per release. +4. Flaky test percentage. +5. Mean time to diagnose and resolve failures. + +## 30-Day Execution Plan + +Week 1: + +1. Baseline scripts and deterministic environment. +2. Restore lint/test commands to green status. +3. Add structured logging and IDs. + +Week 2: + +1. Spec template and mandatory spec policy. +2. Contract tests for core backend routes. +3. First diagnostics bundle version. + +Week 3: + +1. AI policy and bounded autonomous edit/run loop. +2. Regression-test-first bugfix workflow. +3. CI artifact enrichment and runbook mapping. + +Week 4: + +1. Pilot autonomous feature tasks in low-risk areas. +2. Measure success/failure patterns. +3. Expand scope only if quality gates hold. + +## Notes for TalkEdit + +1. Keep router files thin and service logic isolated to improve AI edit precision. +2. Preserve compatibility in desktop bridge contracts to avoid frontend breakage. +3. Treat export/transcription pipeline changes as high-risk and always require regression tests. +4. Keep Linux WebKit startup and media URL consistency as explicit regression targets. diff --git a/docs/ai-policy.md b/docs/ai-policy.md new file mode 100644 index 0000000..8abd09d --- /dev/null +++ b/docs/ai-policy.md @@ -0,0 +1,73 @@ +# AI Execution Policy + +Purpose: define what autonomous AI can do in this repository without explicit human approval. + +## Default Mode + +- AI may implement and debug within approved scope. +- AI must run validation commands after code changes. +- AI must stop and escalate when blocked by policy or ambiguity. + +## Allowed Autonomous Actions + +1. Edit frontend, backend, shared schema, docs, and scripts. +2. Add/modify tests related to the task. +3. Run non-destructive validation commands. +4. Update project docs and Copilot instructions when behavior changes. + +## Restricted Actions (Require Approval) + +1. Security/privacy-sensitive logic changes. +2. Data migrations or destructive file operations. +3. Credential handling changes or secrets management changes. +4. Breaking API/schema changes. +5. Build/release signing, packaging, and deployment automation changes. + +## Prohibited Actions + +1. Destructive git commands (`git reset --hard`, force pushing protected branches). +2. Deleting user project/media data. +3. Bypassing required checks in CI. + +## Required Validation Workflow + +For each autonomous task: + +1. Implement smallest safe change set. +2. Run lint/type/test/build checks for impacted scope. +3. Inspect errors and fix with bounded retries. +4. Re-run checks until green or escalated. +5. Produce concise summary with risks and assumptions. + +## Escalation Triggers + +AI must ask a human when: + +1. Requirements are ambiguous and affect user-visible behavior. +2. Multiple product choices are plausible with no clear preference. +3. Potential legal, security, or compliance impact exists. +4. CI remains failing after 3 repair attempts in the same area. +5. A requested operation conflicts with this policy. + +## Required Artifacts In AI PR/Change Summary + +1. What changed. +2. Why it changed. +3. Validation commands and outcome. +4. Residual risks. +5. Follow-up tasks. + +## Risk Levels + +- Low: docs, styling, isolated refactors, non-critical bugfixes. +- Medium: feature additions with contract-stable behavior. +- High: API/schema/security/export pipeline/transcription pipeline changes. + +High-risk changes require explicit human review before merge. + +## TalkEdit-Specific Rules + +1. Preserve compatibility for desktop bridge contracts unless explicitly approved. +2. Keep routers thin and business logic in backend services. +3. Export/transcription pipeline changes must include regression tests. +4. Linux WebKit startup behavior and media URL consistency are mandatory regression targets. diff --git a/docs/runbooks/error-codes.md b/docs/runbooks/error-codes.md new file mode 100644 index 0000000..4f0b260 --- /dev/null +++ b/docs/runbooks/error-codes.md @@ -0,0 +1,113 @@ +# Error Codes Runbook + +Purpose: provide consistent, AI-readable error categories for faster autonomous debugging. + +## Format + +Use codes in this format: `--` + +Examples: + +- `BE-EXPORT-001` +- `FE-WAVEFORM-002` +- `HOST-BRIDGE-003` + +## Backend (FastAPI / Services) + +### Export + +- `BE-EXPORT-001`: Export request validation failed. + - Symptoms: HTTP 400, missing/invalid ranges. + - Likely causes: malformed payload, empty segments. + - First checks: request body shape, keep/mute/gain ranges. + +- `BE-EXPORT-002`: FFmpeg command failed. + - Symptoms: HTTP 500, stderr contains filter/codec error. + - Likely causes: invalid filter chain, unsupported codec/container. + - First checks: generated FFmpeg args, source media codec, target format. + +- `BE-EXPORT-003`: Caption burn-in/subtitle generation failed. + - Symptoms: burn-in export fails while plain export works. + - Likely causes: ASS generation issue, subtitle path/temp file cleanup race. + - First checks: ASS file generation, temp file lifecycle. + +### Transcription + +- `BE-TRANSCRIBE-001`: Model unavailable or download failure. + - Symptoms: transcription never starts or exits early. + - Likely causes: missing model, network/cache issue. + - First checks: model cache path, ensure-model logs. + +- `BE-TRANSCRIBE-002`: Inference pipeline runtime failure. + - Symptoms: mid-run crash, partial output. + - Likely causes: CUDA/CPU mismatch, unsupported media, resource exhaustion. + - First checks: environment, GPU availability, media decoding logs. + +### Audio / Waveform + +- `BE-AUDIO-001`: Waveform endpoint failed. + - Symptoms: waveform panel shows unavailable/error. + - Likely causes: decode error, invalid file path, unsupported media input. + - First checks: `audio/waveform` response body, file existence, FFmpeg decode path. + +## Frontend (React) + +### Timeline / Zones + +- `FE-TIMELINE-001`: Zone interaction state inconsistency. + - Symptoms: cannot drag/select/delete zones predictably. + - Likely causes: stale selection/editing state, hidden/selected mismatch. + - First checks: zone mode flags, selectedZone state transitions. + +- `FE-TIMELINE-002`: Visibility filter mismatch. + - Symptoms: hidden zones still interactive or selected. + - Likely causes: hit-testing ignores visibility flags. + - First checks: hit-test filters and selected-zone reset logic. + +### Media UI + +- `FE-WAVEFORM-001`: Waveform fetch failed. + - Symptoms: warning banner with URL/error. + - Likely causes: backend unavailable, bad path encoding, CORS/proxy issue. + - First checks: backend health endpoint, waveform URL, network tab logs. + +- `FE-PROJECT-001`: Project load mismatch. + - Symptoms: loaded media/transcript differs from saved data. + - Likely causes: schema drift, fallback URL mismatch. + - First checks: project schema fields, loadVideo/loadProject URL parity. + +## Host / Bridge (Tauri) + +- `HOST-BRIDGE-001`: Desktop API bridge unavailable. + - Symptoms: open/save/transcribe actions no-op or throw. + - Likely causes: bridge init error, host command mismatch. + - First checks: bridge initialization, command names, runtime environment. + +- `HOST-WEBKIT-001`: Linux WebKit startup/render regression. + - Symptoms: noisy startup errors, UI load issues. + - Likely causes: CSP/font regressions, unsupported protocol calls. + - First checks: CSP config, remote font usage, bridge fallback behavior. + +## Logging Guidance + +When raising errors, include: + +1. Error code. +2. Human message. +3. Correlation/request id. +4. Relevant paths/ids (sanitized). +5. Suggested first-check hints. + +Example structured payload: + +```json +{ + "code": "BE-EXPORT-002", + "message": "FFmpeg export failed", + "requestId": "exp_20260415_001", + "context": { + "format": "mp4", + "mode": "reencode" + } +} +``` diff --git a/docs/spec-template.md b/docs/spec-template.md new file mode 100644 index 0000000..eee2ab0 --- /dev/null +++ b/docs/spec-template.md @@ -0,0 +1,113 @@ +# Feature Spec Template + +Use this template for every net-new feature and major behavior change. + +## Metadata + +- Spec ID: SPEC-YYYYMMDD- +- Owner: +- Date: +- Status: draft | approved | in-progress | done +- Related issue/PR: + +## Problem Statement + +Describe the user problem in 2-5 sentences. + +## User Story + +As a , I want , so that . + +## Scope + +### In Scope + +1. +2. +3. + +### Out of Scope + +1. +2. + +## Functional Requirements + +1. +2. +3. + +## Acceptance Criteria + +1. Given , when , then . +2. Given , when , then . +3. Failure handling is deterministic and user-visible. + +## UX Notes + +- Entry points (toolbar/panel/command): +- Empty/loading/error states: +- Keyboard shortcuts / accessibility expectations: + +## API And Data Contracts + +- Endpoints impacted: +- Request/response changes: +- Backward compatibility plan: +- Project schema impact (`shared/project-schema.json`): + +## Architecture Impact + +- Frontend files/components likely affected: +- Backend routers/services likely affected: +- Tauri/bridge changes required: + +## Risks + +1. +2. + +## Test Plan + +### Unit Tests + +1. +2. + +### Integration Tests + +1. +2. + +### E2E / Smoke Tests + +1. +2. + +### Regression Tests + +List known regressions this spec must prevent. + +## Observability + +- New logs/error codes: +- Metrics/traces needed: +- Diagnostics artifacts expected on failure: + +## Rollout Plan + +1. Development and internal validation. +2. Staged rollout or feature flag (if applicable). +3. Rollback path. + +## Open Questions + +1. +2. + +## Definition Of Done + +1. Acceptance criteria pass. +2. Tests added and green. +3. Docs/instructions updated. +4. Risks and assumptions recorded in PR summary. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..a535afc --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,26 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist', 'node_modules'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 94702eb..a1a5040 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,14 +20,20 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@eslint/js": "^9.39.4", "@tauri-apps/cli": "^2", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "^10.4.20", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.0", "typescript": "^5.7.0", + "typescript-eslint": "^8.58.2", "vite": "^6.0.0" } }, @@ -768,6 +774,215 @@ "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.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "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.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "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.5", + "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.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "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", @@ -1510,6 +1725,13 @@ "dev": true, "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/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1530,6 +1752,301 @@ "@types/react": "^19.2.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.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.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.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/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1551,6 +2068,62 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "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.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1579,6 +2152,13 @@ "dev": true, "license": "MIT" }, + "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/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -1616,6 +2196,13 @@ "postcss": "^8.1.0" } }, + "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/baseline-browser-mapping": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", @@ -1642,6 +2229,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1689,6 +2287,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1720,6 +2328,23 @@ ], "license": "CC-BY-4.0" }, + "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": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1758,6 +2383,26 @@ "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/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1768,6 +2413,13 @@ "node": ">= 6" } }, + "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/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1775,6 +2427,21 @@ "dev": true, "license": "MIT" }, + "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/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1813,6 +2480,13 @@ } } }, + "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/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1886,6 +2560,210 @@ "node": ">=6" } }, + "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.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@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.14.0", + "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.5", + "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-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "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/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/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/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/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-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1916,6 +2794,20 @@ "node": ">= 6" } }, + "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/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -1926,6 +2818,19 @@ "reusify": "^1.0.4" } }, + "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/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1939,6 +2844,44 @@ "node": ">=8" } }, + "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.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2001,6 +2944,29 @@ "node": ">=10.13.0" } }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", @@ -2014,6 +2980,60 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "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-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2076,6 +3096,13 @@ "node": ">=0.12.0" } }, + "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": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -2093,6 +3120,19 @@ "dev": true, "license": "MIT" }, + "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/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -2106,6 +3146,27 @@ "node": ">=6" } }, + "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/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2119,6 +3180,30 @@ "node": ">=6" } }, + "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/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/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2139,6 +3224,29 @@ "dev": true, "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.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/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2182,6 +3290,19 @@ "node": ">=8.6" } }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2220,6 +3341,13 @@ "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/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2257,6 +3385,89 @@ "node": ">= 6" } }, + "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", @@ -2467,6 +3678,26 @@ "dev": true, "license": "MIT" }, + "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/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2573,6 +3804,16 @@ "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/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -2669,6 +3910,29 @@ "semver": "bin/semver.js" } }, + "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/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2679,6 +3943,19 @@ "node": ">=0.10.0" } }, + "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/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -2702,6 +3979,19 @@ "node": ">=16 || 14 >=14.17" } }, + "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", @@ -2837,6 +4127,19 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -2844,6 +4147,19 @@ "dev": true, "license": "Apache-2.0" }, + "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", @@ -2858,6 +4174,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2889,6 +4229,16 @@ "browserslist": ">= 4.21.0" } }, + "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/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3008,6 +4358,32 @@ "integrity": "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==", "license": "BSD-3-Clause" }, + "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/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/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3015,6 +4391,42 @@ "dev": true, "license": "ISC" }, + "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/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zundo": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/zundo/-/zundo-2.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3c56bf7..eaa5e5e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,14 +22,20 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@eslint/js": "^9.39.4", "@tauri-apps/cli": "^2", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "^10.4.20", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.0", "typescript": "^5.7.0", + "typescript-eslint": "^8.58.2", "vite": "^6.0.0" } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7889344..9b6917b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -22,6 +22,7 @@ import { Scissors, VolumeX, Volume2, + SlidersHorizontal, FilePlus2, RefreshCw, } from 'lucide-react'; @@ -57,6 +58,7 @@ export default function App() { selectedWordIndices, addCutRange, addMuteRange, + addGainRange, } = useEditorStore(); const [activePanel, setActivePanel] = useState(null); @@ -64,6 +66,8 @@ export default function App() { const [whisperModel, setWhisperModel] = useState('base'); const [cutMode, setCutMode] = useState(false); const [muteMode, setMuteMode] = useState(false); + const [gainMode, setGainMode] = useState(false); + const [gainModeDb, setGainModeDb] = useState(3); const [showReprocessConfirm, setShowReprocessConfirm] = useState(false); const [showUnsavedPrompt, setShowUnsavedPrompt] = useState(false); const [pendingProceedAction, setPendingProceedAction] = useState<(() => Promise) | null>(null); @@ -133,12 +137,13 @@ export default function App() { useKeyboardShortcuts(); - // Handle Escape key to exit cut/mute modes + // Handle Escape key to exit timeline zone modes useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { setCutMode(false); setMuteMode(false); + setGainMode(false); } }; @@ -236,6 +241,7 @@ export default function App() { setManualPath(''); setCutMode(false); setMuteMode(false); + setGainMode(false); }); }; @@ -348,6 +354,7 @@ export default function App() { // Toggle cut mode setCutMode(!cutMode); setMuteMode(false); // Exit mute mode + setGainMode(false); // Exit gain mode } }; @@ -362,6 +369,20 @@ export default function App() { // Toggle mute mode setMuteMode(!muteMode); setCutMode(false); // Exit cut mode + setGainMode(false); // Exit gain mode + } + }; + + const handleGain = () => { + if (selectedWordIndices.length > 0) { + const sorted = [...selectedWordIndices].sort((a, b) => a - b); + const startTime = words[sorted[0]].start; + const endTime = words[sorted[sorted.length - 1]].end; + addGainRange(startTime, endTime, gainModeDb); + } else { + setGainMode(!gainMode); + setCutMode(false); + setMuteMode(false); } }; @@ -518,6 +539,12 @@ export default function App() { onClick={handleMute} active={muteMode} /> + } + label="Gain Zone" + onClick={handleGain} + active={gainMode} + /> } label="Volume" @@ -635,14 +662,21 @@ export default function App() { {/* Waveform timeline */}
- +
{/* Right panel (AI / Export / Settings) */} {activePanel && (
- {activePanel === 'volume' && } + {activePanel === 'volume' && ( + + )} {activePanel === 'silence' && } {activePanel === 'ai' && } {activePanel === 'export' && } diff --git a/frontend/src/components/VolumePanel.tsx b/frontend/src/components/VolumePanel.tsx index 94813d8..c979323 100644 --- a/frontend/src/components/VolumePanel.tsx +++ b/frontend/src/components/VolumePanel.tsx @@ -2,7 +2,19 @@ import { useMemo, useState } from 'react'; import { useEditorStore } from '../store/editorStore'; import { Trash2, Volume2 } from 'lucide-react'; -export default function VolumePanel() { +interface VolumePanelProps { + gainMode: boolean; + onToggleGainMode: () => void; + timelineGainDb: number; + onTimelineGainDbChange: (gainDb: number) => void; +} + +export default function VolumePanel({ + gainMode, + onToggleGainMode, + timelineGainDb, + onTimelineGainDbChange, +}: VolumePanelProps) { const { words, selectedWordIndices, @@ -65,6 +77,34 @@ export default function VolumePanel() {
+
+ +
+ onTimelineGainDbChange(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))} + className="w-24 px-2 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" + /> + +
+

+ In gain zone mode, drag on the timeline to create a zone with this dB value. +

+
+
diff --git a/frontend/src/components/WaveformTimeline.tsx b/frontend/src/components/WaveformTimeline.tsx index 934cc01..83affb8 100644 --- a/frontend/src/components/WaveformTimeline.tsx +++ b/frontend/src/components/WaveformTimeline.tsx @@ -103,7 +103,17 @@ function pickInterval(pxPerSec: number): { major: number; minor: number } { return { major, minor }; } -export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boolean; muteMode: boolean }) { +export default function WaveformTimeline({ + cutMode, + muteMode, + gainMode, + gainModeDb, +}: { + cutMode: boolean; + muteMode: boolean; + gainMode: boolean; + gainModeDb: number; +}) { const waveCanvasRef = useRef(null); const headCanvasRef = useRef(null); const containerRef = useRef(null); @@ -116,13 +126,17 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole const deletedRanges = useEditorStore((s) => s.deletedRanges); const cutRanges = useEditorStore((s) => s.cutRanges); const muteRanges = useEditorStore((s) => s.muteRanges); + const gainRanges = useEditorStore((s) => s.gainRanges); const setCurrentTime = useEditorStore((s) => s.setCurrentTime); const addCutRange = useEditorStore((s) => s.addCutRange); const addMuteRange = useEditorStore((s) => s.addMuteRange); + const addGainRange = useEditorStore((s) => s.addGainRange); const updateCutRange = useEditorStore((s) => s.updateCutRange); const updateMuteRange = useEditorStore((s) => s.updateMuteRange); + const updateGainRangeBounds = useEditorStore((s) => s.updateGainRangeBounds); const removeCutRange = useEditorStore((s) => s.removeCutRange); const removeMuteRange = useEditorStore((s) => s.removeMuteRange); + const removeGainRange = useEditorStore((s) => s.removeGainRange); const waveformDataRef = useRef(null); const zoomRef = useRef(1); // 1 = show all, >1 = zoomed in @@ -135,10 +149,13 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole const selectionStartRef = useRef(null); const [selectionStart, setSelectionStart] = useState(null); const [selectionEnd, setSelectionEnd] = useState(null); - const [selectedZone, setSelectedZone] = useState<{type: 'cut' | 'mute', id: string} | null>(null); - const [editingZone, setEditingZone] = useState<{type: 'cut' | 'mute', id: string, edge: 'start' | 'end' | 'move'} | null>(null); + const [selectedZone, setSelectedZone] = useState<{type: 'cut' | 'mute' | 'gain', id: string} | null>(null); + const [editingZone, setEditingZone] = useState<{type: 'cut' | 'mute' | 'gain', id: string, edge: 'start' | 'end' | 'move'} | null>(null); const [hoverCursor, setHoverCursor] = useState('crosshair'); - const editingZoneRef = useRef<{type: 'cut' | 'mute', id: string, edge: 'start' | 'end' | 'move'} | null>(null); + const editingZoneRef = useRef<{type: 'cut' | 'mute' | 'gain', id: string, edge: 'start' | 'end' | 'move'} | null>(null); + const [showCutZones, setShowCutZones] = useState(true); + const [showMuteZones, setShowMuteZones] = useState(true); + const [showGainZones, setShowGainZones] = useState(true); useEffect(() => { if (!videoUrl || !videoPath) return; @@ -304,7 +321,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole } // Draw cut ranges (red overlays) - for (const range of cutRanges) { + for (const range of showCutZones ? cutRanges : []) { const x1 = (range.start - scroll) * pxPerSec; const x2 = (range.end - scroll) * pxPerSec; const isSelected = selectedZone?.type === 'cut' && selectedZone.id === range.id; @@ -329,7 +346,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole } // Draw mute ranges (blue overlays) - for (const range of muteRanges) { + for (const range of showMuteZones ? muteRanges : []) { const x1 = (range.start - scroll) * pxPerSec; const x2 = (range.end - scroll) * pxPerSec; const isSelected = selectedZone?.type === 'mute' && selectedZone.id === range.id; @@ -353,15 +370,45 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole } } - // Draw selection overlay (when in cut/mute mode) - if ((cutMode || muteMode) && selectionStart !== null && selectionEnd !== null) { + // Draw gain ranges (amber overlays) + for (const range of showGainZones ? gainRanges : []) { + const x1 = (range.start - scroll) * pxPerSec; + const x2 = (range.end - scroll) * pxPerSec; + const isSelected = selectedZone?.type === 'gain' && selectedZone.id === range.id; + + ctx.fillStyle = isSelected ? 'rgba(245, 158, 11, 0.55)' : 'rgba(245, 158, 11, 0.35)'; + ctx.fillRect(x1, waveTop, x2 - x1, waveH); + + if (isSelected) { + ctx.strokeStyle = '#f59e0b'; + ctx.lineWidth = 2; + ctx.strokeRect(x1, waveTop, x2 - x1, waveH); + + ctx.fillStyle = '#f59e0b'; + ctx.beginPath(); + ctx.arc(x1, waveTop + waveH / 2, 4, 0, 2 * Math.PI); + ctx.fill(); + ctx.beginPath(); + ctx.arc(x2, waveTop + waveH / 2, 4, 0, 2 * Math.PI); + ctx.fill(); + } + } + + // Draw selection overlay (when in zone mode) + if ((cutMode || muteMode || gainMode) && selectionStart !== null && selectionEnd !== null) { const x1 = (Math.min(selectionStart, selectionEnd) - scroll) * pxPerSec; const x2 = (Math.max(selectionStart, selectionEnd) - scroll) * pxPerSec; - ctx.fillStyle = cutMode ? 'rgba(239, 68, 68, 0.5)' : 'rgba(59, 130, 246, 0.5)'; + const fillColor = cutMode + ? 'rgba(239, 68, 68, 0.5)' + : muteMode + ? 'rgba(59, 130, 246, 0.5)' + : 'rgba(245, 158, 11, 0.5)'; + const strokeColor = cutMode ? '#ef4444' : muteMode ? '#3b82f6' : '#f59e0b'; + ctx.fillStyle = fillColor; ctx.fillRect(x1, waveTop, x2 - x1, waveH); // Add border - ctx.strokeStyle = cutMode ? '#ef4444' : '#3b82f6'; + ctx.strokeStyle = strokeColor; ctx.lineWidth = 2; ctx.strokeRect(x1, waveTop, x2 - x1, waveH); } @@ -389,7 +436,21 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole ctx.lineTo(x, mid + max * amp); } ctx.stroke(); - }, [deletedRanges, cutRanges, muteRanges, selectionStart, selectionEnd, cutMode, muteMode, selectedZone]); + }, [ + deletedRanges, + cutRanges, + muteRanges, + gainRanges, + selectionStart, + selectionEnd, + cutMode, + muteMode, + gainMode, + selectedZone, + showCutZones, + showMuteZones, + showGainZones, + ]); // Keep the ref in sync with the latest drawStaticWaveform closure useEffect(() => { @@ -537,7 +598,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole const handleSize = forHover ? 6 : 8; // Smaller hit area for hover, larger for click // Check cut ranges - for (const range of cutRanges) { + for (const range of showCutZones ? cutRanges : []) { const rangeX1 = (range.start - scroll) * pxPerSec; const rangeX2 = (range.end - scroll) * pxPerSec; const isSelected = selectedZone?.type === 'cut' && selectedZone.id === range.id; @@ -579,7 +640,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole } // Check mute ranges - for (const range of muteRanges) { + for (const range of showMuteZones ? muteRanges : []) { const rangeX1 = (range.start - scroll) * pxPerSec; const rangeX2 = (range.end - scroll) * pxPerSec; const isSelected = selectedZone?.type === 'mute' && selectedZone.id === range.id; @@ -620,8 +681,43 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole } } + // Check gain ranges + for (const range of showGainZones ? gainRanges : []) { + const rangeX1 = (range.start - scroll) * pxPerSec; + const rangeX2 = (range.end - scroll) * pxPerSec; + const isSelected = selectedZone?.type === 'gain' && selectedZone.id === range.id; + + if (forHover && isSelected) { + if (Math.abs(x - rangeX1) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'start' as const }; + } + if (Math.abs(x - rangeX2) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'end' as const }; + } + } else if (!forHover) { + if (isSelected) { + if (Math.abs(x - rangeX1) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'start' as const }; + } + if (Math.abs(x - rangeX2) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'end' as const }; + } + } else { + if (Math.abs(x - rangeX1) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'start' as const }; + } + if (Math.abs(x - rangeX2) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) { + return { type: 'gain' as const, id: range.id, edge: 'end' as const }; + } + } + if (x >= rangeX1 && x <= rangeX2) { + return { type: 'gain' as const, id: range.id, edge: 'move' as const }; + } + } + } + return null; - }, [cutRanges, muteRanges, selectedZone]); + }, [cutRanges, muteRanges, gainRanges, selectedZone, showCutZones, showMuteZones, showGainZones]); const handleMouseMove = useCallback((e: React.MouseEvent) => { if (isDragging) return; // Don't change cursor while dragging @@ -654,9 +750,11 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole setIsDragging(true); const startTime = clientXToTime(e.clientX); - const originalRange = zoneHit.type === 'cut' + const originalRange = zoneHit.type === 'cut' ? cutRanges.find(r => r.id === zoneHit.id) - : muteRanges.find(r => r.id === zoneHit.id); + : zoneHit.type === 'mute' + ? muteRanges.find(r => r.id === zoneHit.id) + : gainRanges.find(r => r.id === zoneHit.id); if (!originalRange) return; @@ -686,8 +784,10 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole if (newStart < newEnd) { if (editingZoneRef.current.type === 'cut') { updateCutRange(editingZoneRef.current.id, newStart, newEnd); - } else { + } else if (editingZoneRef.current.type === 'mute') { updateMuteRange(editingZoneRef.current.id, newStart, newEnd); + } else { + updateGainRangeBounds(editingZoneRef.current.id, newStart, newEnd); } } }; @@ -710,7 +810,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole setSelectedZone(null); setEditingZone(null); - if (cutMode || muteMode) { + if (cutMode || muteMode || gainMode) { // Range selection mode const startTime = clientXToTime(e.clientX); selectionStartRef.current = startTime; @@ -737,6 +837,8 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole addCutRange(start, end); } else if (muteMode) { addMuteRange(start, end); + } else if (gainMode) { + addGainRange(start, end, gainModeDb); } } @@ -771,7 +873,7 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole window.addEventListener('mouseup', onUp); } }, - [cutMode, muteMode, clientXToTime, seekToClientX, addCutRange, addMuteRange, selectionEnd, getZoneAtPosition], + [cutMode, muteMode, gainMode, gainModeDb, clientXToTime, seekToClientX, addCutRange, addMuteRange, addGainRange, selectionEnd, getZoneAtPosition, cutRanges, muteRanges, gainRanges, duration, updateCutRange, updateMuteRange, updateGainRangeBounds], ); // Handle keyboard shortcuts for zone editing @@ -793,8 +895,10 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole e.stopImmediatePropagation(); if (selectedZone.type === 'cut') { removeCutRange(selectedZone.id); - } else { + } else if (selectedZone.type === 'mute') { removeMuteRange(selectedZone.id); + } else { + removeGainRange(selectedZone.id); } setSelectedZone(null); setEditingZone(null); @@ -806,7 +910,14 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole // Capture phase ensures zone delete runs before app-level bubble shortcuts. window.addEventListener('keydown', handleKeyDown, { capture: true }); return () => window.removeEventListener('keydown', handleKeyDown, { capture: true }); - }, [selectedZone, removeCutRange, removeMuteRange]); + }, [selectedZone, removeCutRange, removeMuteRange, removeGainRange]); + + useEffect(() => { + if (!selectedZone) return; + if (selectedZone.type === 'cut' && !showCutZones) setSelectedZone(null); + if (selectedZone.type === 'mute' && !showMuteZones) setSelectedZone(null); + if (selectedZone.type === 'gain' && !showGainZones) setSelectedZone(null); + }, [selectedZone, showCutZones, showMuteZones, showGainZones]); if (!videoUrl) { return ( @@ -818,13 +929,41 @@ export default function WaveformTimeline({ cutMode, muteMode }: { cutMode: boole return (
-
- - Timeline - - - Scroll · Ctrl+Scroll to zoom - +
+
+ + Timeline + + {cutMode && Cut mode} + {muteMode && Mute mode} + {gainMode && Gain mode ({gainModeDb.toFixed(1)} dB)} +
+
+ + + + + Scroll · Ctrl+Scroll to zoom + +
{audioError ? (
diff --git a/frontend/src/store/editorStore.ts b/frontend/src/store/editorStore.ts index ba63805..5f1768e 100644 --- a/frontend/src/store/editorStore.ts +++ b/frontend/src/store/editorStore.ts @@ -65,6 +65,7 @@ interface EditorActions { addGainRange: (start: number, end: number, gainDb: number) => void; updateCutRange: (id: string, start: number, end: number) => void; updateMuteRange: (id: string, start: number, end: number) => void; + updateGainRangeBounds: (id: string, start: number, end: number) => void; updateGainRange: (id: string, gainDb: number) => void; removeCutRange: (id: string) => void; removeMuteRange: (id: string) => void; @@ -299,6 +300,15 @@ export const useEditorStore = create()( }); }, + updateGainRangeBounds: (id, start, end) => { + const { gainRanges } = get(); + set({ + gainRanges: gainRanges.map((r) => + r.id === id ? { ...r, start, end } : r + ), + }); + }, + updateGainRange: (id, gainDb) => { const { gainRanges } = get(); set({ diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index e72183c..1e5f2fe 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AIPanel.tsx","./src/components/DevPanel.tsx","./src/components/ExportDialog.tsx","./src/components/SettingsPanel.tsx","./src/components/TranscriptEditor.tsx","./src/components/VideoPlayer.tsx","./src/components/WaveformTimeline.tsx","./src/hooks/useKeyboardShortcuts.ts","./src/hooks/useVideoSync.ts","./src/lib/dev-logger.ts","./src/lib/tauri-bridge.ts","./src/store/aiStore.ts","./src/store/editorStore.ts","./src/types/project.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/AIPanel.tsx","./src/components/DevPanel.tsx","./src/components/ExportDialog.tsx","./src/components/SettingsPanel.tsx","./src/components/SilenceTrimmerPanel.tsx","./src/components/TranscriptEditor.tsx","./src/components/VideoPlayer.tsx","./src/components/VolumePanel.tsx","./src/components/WaveformTimeline.tsx","./src/hooks/useKeyboardShortcuts.ts","./src/hooks/useVideoSync.ts","./src/lib/dev-logger.ts","./src/lib/tauri-bridge.ts","./src/store/aiStore.ts","./src/store/editorStore.ts","./src/types/project.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/scripts/collect-diagnostics.sh b/scripts/collect-diagnostics.sh new file mode 100755 index 0000000..4b82060 --- /dev/null +++ b/scripts/collect-diagnostics.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_BASE="$ROOT_DIR/.diagnostics" +TS="$(date +%Y%m%d_%H%M%S)" +OUT_DIR="$OUT_BASE/diag_$TS" + +mkdir -p "$OUT_DIR" + +log() { + printf '[collect-diagnostics] %s\n' "$1" +} + +capture_cmd() { + local name="$1" + shift + { + echo "# $name" + echo "# cmd: $*" + "$@" + } >"$OUT_DIR/$name.txt" 2>&1 || true +} + +log "output: $OUT_DIR" + +capture_cmd "env_uname" uname -a +capture_cmd "env_node_version" node --version +capture_cmd "env_npm_version" npm --version +capture_cmd "env_git_status" git -C "$ROOT_DIR" status --short +capture_cmd "env_git_head" git -C "$ROOT_DIR" rev-parse --short HEAD + +if [[ -f "$ROOT_DIR/webview.log" ]]; then + cp "$ROOT_DIR/webview.log" "$OUT_DIR/webview.log" || true +fi + +if [[ -f "$ROOT_DIR/backend.log" ]]; then + cp "$ROOT_DIR/backend.log" "$OUT_DIR/backend.log" || true +fi + +if [[ -f "$ROOT_DIR/frontend/package.json" ]]; then + capture_cmd "frontend_lint" bash -lc "cd '$ROOT_DIR/frontend' && npm run -s lint" + capture_cmd "frontend_build" bash -lc "cd '$ROOT_DIR/frontend' && npm run -s build" +fi + +PY="" +for p in \ + "$ROOT_DIR/.venv312/bin/python3.12" \ + "$ROOT_DIR/.venv312/bin/python" \ + "$ROOT_DIR/.venv/bin/python3" \ + "$ROOT_DIR/.venv/bin/python" \ + "$ROOT_DIR/venv/bin/python3" \ + "$ROOT_DIR/venv/bin/python"; do + if [[ -x "$p" ]]; then + PY="$p" + break + fi +done + +if [[ -n "$PY" ]]; then + capture_cmd "backend_python_version" "$PY" --version + capture_cmd "backend_health_check" "$PY" -c "import importlib; importlib.import_module('backend.main'); print('backend import OK')" +fi + +capture_cmd "list_recent_files" find "$ROOT_DIR" -maxdepth 2 -type f | head -n 200 + +if command -v tar >/dev/null 2>&1; then + tar -czf "$OUT_DIR.tar.gz" -C "$OUT_BASE" "diag_$TS" + log "archive: $OUT_DIR.tar.gz" +fi + +log "done" diff --git a/scripts/validate-all.sh b/scripts/validate-all.sh new file mode 100755 index 0000000..4c9c95b --- /dev/null +++ b/scripts/validate-all.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +log() { + printf '[validate-all] %s\n' "$1" +} + +run_if_present() { + local cmd="$1" + if command -v ${cmd%% *} >/dev/null 2>&1; then + eval "$cmd" + return 0 + fi + return 1 +} + +log "root: $ROOT_DIR" +cd "$ROOT_DIR" + +log "Step 1/5: frontend dependency check" +if [[ ! -d "frontend/node_modules" ]]; then + log "frontend/node_modules missing; install with: cd frontend && npm install" +fi + +log "Step 2/5: frontend lint" +if [[ -f "frontend/package.json" ]]; then + ( + cd frontend + if npm run -s lint; then + log "frontend lint: OK" + else + log "frontend lint failed" + exit 1 + fi + ) +else + log "frontend/package.json not found; skipping" +fi + +log "Step 3/5: frontend build" +if [[ -f "frontend/package.json" ]]; then + ( + cd frontend + if npm run -s build; then + log "frontend build: OK" + else + log "frontend build failed" + exit 1 + fi + ) +fi + +log "Step 4/5: backend syntax check" +PY="" +for p in \ + "$ROOT_DIR/.venv312/bin/python3.12" \ + "$ROOT_DIR/.venv312/bin/python" \ + "$ROOT_DIR/.venv/bin/python3" \ + "$ROOT_DIR/.venv/bin/python" \ + "$ROOT_DIR/venv/bin/python3" \ + "$ROOT_DIR/venv/bin/python"; do + if [[ -x "$p" ]]; then + PY="$p" + break + fi +done + +if [[ -n "$PY" ]]; then + log "using python: $PY" + "$PY" -m py_compile "$ROOT_DIR/backend/main.py" "$ROOT_DIR/backend/routers/export.py" + log "backend syntax check: OK" +else + log "no project python found (.venv312/.venv/venv); skipping backend syntax check" +fi + +log "Step 5/5: backend health import smoke" +if [[ -n "$PY" ]]; then + "$PY" - <<'PYCODE' +import importlib +mods = [ + "backend.main", + "backend.routers.export", + "backend.services.video_editor", +] +for m in mods: + importlib.import_module(m) +print("backend import smoke: OK") +PYCODE +fi + +log "Validation complete"