From 78901a8076e6e610f850f306a4dfce070c4b7eec Mon Sep 17 00:00:00 2001 From: samirpatil2000 Date: Fri, 3 Apr 2026 15:16:45 +0530 Subject: [PATCH 001/152] feat: configure macOS hardened runtime, entitlements, and build environment variables for notarization --- .env.example | 10 ++ .gitignore | 1 + electron-builder.json5 | 5 +- macos.entitlements | 25 +++++ scripts/build_macos.sh | 216 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 macos.entitlements create mode 100755 scripts/build_macos.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce0e08b --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +APP_NAME=Openscreen +BUNDLE_ID=com.siddharthvaddem.openscreen + +APPLE_ID= +TEAM_ID= +SIGN_IDENTITY="Developer ID Application: Samir Patil ()" +CSC_NAME="Samir Patil ()" + +NOTARY_PROFILE=OpenScreen-notary +APPLE_APP_SPECIFIC_PASSWORD= diff --git a/.gitignore b/.gitignore index 70cc387..74018f5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist dist-electron dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/electron-builder.json5 b/electron-builder.json5 index a8f1dc1..8e938ad 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -28,7 +28,10 @@ ], "mac": { - "hardenedRuntime": false, + "notarize": false, + "hardenedRuntime": true, + "entitlements": "macos.entitlements", + "entitlementsInherit": "macos.entitlements", "target": [ { "target": "dmg", diff --git a/macos.entitlements b/macos.entitlements new file mode 100644 index 0000000..5c6ddcf --- /dev/null +++ b/macos.entitlements @@ -0,0 +1,25 @@ + + + + + + com.apple.security.cs.allow-jit + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + + com.apple.security.cs.disable-library-validation + + + + com.apple.security.device.audio-input + + + + com.apple.security.device.camera + + + diff --git a/scripts/build_macos.sh b/scripts/build_macos.sh new file mode 100755 index 0000000..bd35710 --- /dev/null +++ b/scripts/build_macos.sh @@ -0,0 +1,216 @@ +#!/bin/bash +# +# OpenScreen macOS Build Script +# Produces: release//OpenScreen-Mac--.dmg +# +# Usage: chmod +x scripts/build_macos.sh && ./scripts/build_macos.sh +# + +set -euo pipefail + +# ── Load .env ───────────────────────────────────────────────────────── +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +ENV_FILE="${PROJECT_ROOT}/.env" + +if [ -f "$ENV_FILE" ]; then + set -a + source "$ENV_FILE" + set +a +else + echo "ERROR: .env file not found at ${ENV_FILE}" + echo "Create one with APP_NAME, SIGN_IDENTITY, NOTARY_PROFILE, etc." + exit 1 +fi + +# ── Config ──────────────────────────────────────────────────────────── +VERSION=$(node -p "require('${PROJECT_ROOT}/package.json').version") +RELEASE_DIR="${PROJECT_ROOT}/release/${VERSION}" +ENTITLEMENTS="${PROJECT_ROOT}/macos.entitlements" +ARCHS=("arm64" "x64") + +# ── Colors ──────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +print_step() { echo -e "\n${CYAN}${BOLD}▸ $1${NC}"; } +print_ok() { echo -e "${GREEN}✓ $1${NC}"; } +print_warn() { echo -e "${YELLOW}⚠ $1${NC}"; } +print_err() { echo -e "${RED}✗ $1${NC}"; } + +# ── Preflight ───────────────────────────────────────────────────────── +echo -e "\n${BOLD}╔══════════════════════════════════════════╗${NC}" +echo -e "${BOLD}║ ${APP_NAME} macOS Build Script v${VERSION} ║${NC}" +echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}" + +print_step "Checking prerequisites..." + +if [[ "$(uname)" != "Darwin" ]]; then + print_err "This script must be run on macOS." + exit 1 +fi +print_ok "Running on macOS ($(uname -m))" + +if ! command -v node &> /dev/null; then + print_err "Node.js not found. Please install Node.js first." + exit 1 +fi +print_ok "Node.js found: $(node -v)" + +if ! command -v npm &> /dev/null; then + print_err "npm not found." + exit 1 +fi +print_ok "npm found: $(npm -v)" + +# Check signing identity +if ! security find-identity -v -p codesigning | grep -q "$SIGN_IDENTITY"; then + print_err "Signing identity not found: ${SIGN_IDENTITY}" + print_err "Run 'security find-identity -v -p codesigning' to see available identities." + exit 1 +fi +print_ok "Signing identity found: ${SIGN_IDENTITY}" + +# Check notary profile +if ! xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" &> /dev/null; then + print_err "Notary profile '${NOTARY_PROFILE}' not found in keychain." + print_err "Run: xcrun notarytool store-credentials \"${NOTARY_PROFILE}\" --apple-id \"${APPLE_ID}\" --team-id \"${TEAM_ID}\"" + exit 1 +fi +print_ok "Notary profile found: ${NOTARY_PROFILE}" + +# Check entitlements +if [ ! -f "$ENTITLEMENTS" ]; then + print_err "Entitlements file not found: ${ENTITLEMENTS}" + exit 1 +fi +print_ok "Entitlements file found" + +# ── Clean ───────────────────────────────────────────────────────────── +cd "$PROJECT_ROOT" + +print_step "Cleaning previous build artifacts..." +rm -rf dist dist-electron "${RELEASE_DIR}" +print_ok "Clean complete" + +# ── Install Dependencies ───────────────────────────────────────────── +print_step "Installing dependencies..." +npm ci +print_ok "Dependencies installed" + +# ── Build Vite + Electron ──────────────────────────────────────────── +print_step "Building Vite + Electron... (this may take a minute)" +npx tsc && npx vite build +print_ok "Vite + Electron build complete" + +# ── Package, Sign, Notarize per Architecture ───────────────────────── +for ARCH in "${ARCHS[@]}"; do + echo "" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BOLD} Building for: ${ARCH}${NC}" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + + # ── Package with electron-builder ───────────────────────────── + print_step "[${ARCH}] Packaging with electron-builder..." + + # Build .app only (--dir), electron-builder handles codesigning + # with hardenedRuntime + entitlements from electron-builder.json5 + CSC_NAME="$CSC_NAME" npx electron-builder --mac --${ARCH} --dir + + # Find the .app bundle + APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | grep -i "${ARCH}\|mac" | head -n1) + if [ -z "$APP_BUNDLE" ]; then + # Fallback: find any .app in the output + APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | head -n1) + fi + + if [ -z "$APP_BUNDLE" ]; then + print_err "[${ARCH}] Could not find .app bundle in ${RELEASE_DIR}" + exit 1 + fi + print_ok "[${ARCH}] App bundle: $(basename "$APP_BUNDLE")" + + # ── Verify codesign on .app ─────────────────────────────────── + print_step "[${ARCH}] Verifying .app code signature..." + codesign --verify --deep --strict "$APP_BUNDLE" 2>&1 || print_warn "[${ARCH}] Deep verify had warnings (may be expected pre-notarization)" + print_ok "[${ARCH}] .app signature verified" + + # ── Create DMG ──────────────────────────────────────────────── + DMG_NAME="${APP_NAME}-Mac-${ARCH}-${VERSION}.dmg" + DMG_OUTPUT="${RELEASE_DIR}/${DMG_NAME}" + DMG_STAGING="${RELEASE_DIR}/dmg-staging-${ARCH}" + + print_step "[${ARCH}] Creating DMG..." + + rm -f "$DMG_OUTPUT" + rm -rf "$DMG_STAGING" + + # Stage: app + Applications shortcut for drag-to-install + mkdir -p "$DMG_STAGING" + cp -R "$APP_BUNDLE" "$DMG_STAGING/" + ln -s /Applications "$DMG_STAGING/Applications" + + hdiutil create \ + -srcfolder "$DMG_STAGING" \ + -volname "${APP_NAME}" \ + -fs HFS+ \ + -fsargs "-c c=64,a=16,e=16" \ + -format UDBZ \ + "$DMG_OUTPUT" + + print_ok "[${ARCH}] DMG created: ${DMG_NAME}" + rm -rf "$DMG_STAGING" + + # ── Sign DMG ────────────────────────────────────────────────── + print_step "[${ARCH}] Signing DMG..." + codesign --force --sign "$SIGN_IDENTITY" --timestamp "$DMG_OUTPUT" + print_ok "[${ARCH}] DMG signed" + + # ── Notarize DMG ────────────────────────────────────────────── + print_step "[${ARCH}] Notarizing DMG with Apple... (this may take several minutes)" + xcrun notarytool submit "$DMG_OUTPUT" \ + --keychain-profile "$NOTARY_PROFILE" \ + --wait + print_ok "[${ARCH}] DMG notarized" + + # ── Staple ──────────────────────────────────────────────────── + print_step "[${ARCH}] Stapling notarization ticket..." + xcrun stapler staple "$DMG_OUTPUT" + print_ok "[${ARCH}] Ticket stapled" + + # ── Validate ────────────────────────────────────────────────── + print_step "[${ARCH}] Validating stapled DMG..." + xcrun stapler validate "$DMG_OUTPUT" + print_ok "[${ARCH}] Validation passed" + +done + +# ── Clean up unpacked dirs (keep only DMGs) ─────────────────────────── +print_step "Cleaning up intermediate directories..." +find "${RELEASE_DIR}" -maxdepth 1 -type d ! -name "$(basename "$RELEASE_DIR")" -exec rm -rf {} + 2>/dev/null || true +print_ok "Cleanup complete" + +# ── Done ────────────────────────────────────────────────────────────── +echo "" +echo -e "${GREEN}${BOLD}════════════════════════════════════════════${NC}" +echo -e "${GREEN}${BOLD} Build & Notarization Complete!${NC}" +echo -e "${GREEN}${BOLD}════════════════════════════════════════════${NC}" +echo "" + +for ARCH in "${ARCHS[@]}"; do + DMG_NAME="${APP_NAME}-Mac-${ARCH}-${VERSION}.dmg" + DMG_PATH="${RELEASE_DIR}/${DMG_NAME}" + if [ -f "$DMG_PATH" ]; then + DMG_SIZE=$(du -h "$DMG_PATH" | cut -f1) + echo -e " 📦 ${BOLD}${ARCH}:${NC} ${DMG_PATH}" + echo -e " 📏 ${BOLD}Size:${NC} ${DMG_SIZE}" + echo "" + fi +done + +echo -e " ${GREEN}All DMGs are fully signed, notarized, and stapled!${NC}" +echo -e " ${GREEN}Ready for distribution outside the Mac App Store.${NC}" +echo "" From f972556443ddbf5577fde31a5d6db93dfa188165 Mon Sep 17 00:00:00 2001 From: lueckpeter76-lgtm Date: Fri, 3 Apr 2026 18:33:54 -0600 Subject: [PATCH 002/152] Revert "fix: prevent double-finalize race condition in restartRecording on Windos" --- src/hooks/useScreenRecorder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 0c418c1..01c3917 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -573,7 +573,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { restarting.current = true; discardRecordingId.current = activeRecordingId; - allowAutoFinalize.current = false; const stopPromises = [ new Promise((resolve) => { From 20b0899c053478becda870debc1b7f91abadf6ee Mon Sep 17 00:00:00 2001 From: dheerajmr01 Date: Sat, 4 Apr 2026 01:37:27 -0500 Subject: [PATCH 003/152] fix: camera light flashes and turns off when clicking webcam button (#308) --- src/components/launch/LaunchWindow.tsx | 1 + src/hooks/useScreenRecorder.ts | 93 +++++++++++++++++--------- src/i18n/locales/en/editor.json | 1 + src/i18n/locales/es/editor.json | 1 + src/i18n/locales/zh-CN/editor.json | 1 + src/lib/requestCameraAccess.ts | 4 +- 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index f1b66b8..8fd8934 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -436,6 +436,7 @@ export function LaunchWindow() { onClick={async () => { await setWebcamEnabled(!webcamEnabled); }} + disabled={recording} title={webcamEnabled ? t("webcam.disableWebcam") : t("webcam.enableWebcam")} > {webcamEnabled diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 0c418c1..ba95e60 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -145,10 +145,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { microphoneStream.current.getTracks().forEach((track) => track.stop()); microphoneStream.current = null; } - if (webcamStream.current) { - webcamStream.current.getTracks().forEach((track) => track.stop()); - webcamStream.current = null; - } if (mixingContext.current) { mixingContext.current.close().catch(() => { // Ignore close errors during recorder teardown. @@ -181,6 +177,66 @@ export function useScreenRecorder(): UseScreenRecorderReturn { [t], ); + useEffect(() => { + if (!webcamEnabled) return; + + let cancelled = false; + let acquiredStream: MediaStream | null = null; + + const acquire = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + audio: false, + video: webcamDeviceId + ? { + deviceId: { exact: webcamDeviceId }, + width: { ideal: WEBCAM_TARGET_WIDTH }, + height: { ideal: WEBCAM_TARGET_HEIGHT }, + frameRate: { ideal: WEBCAM_TARGET_FRAME_RATE, max: WEBCAM_TARGET_FRAME_RATE }, + } + : { + width: { ideal: WEBCAM_TARGET_WIDTH }, + height: { ideal: WEBCAM_TARGET_HEIGHT }, + frameRate: { ideal: WEBCAM_TARGET_FRAME_RATE, max: WEBCAM_TARGET_FRAME_RATE }, + }, + }); + + if (cancelled) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + + acquiredStream = stream; + stream.getVideoTracks().forEach((track) => { + track.onended = () => { + webcamStream.current = null; + if (!restarting.current) { + setWebcamEnabledState(false); + toast.error(t("recording.cameraDisconnected")); + } + }; + }); + webcamStream.current = stream; + } catch (cameraError) { + if (!cancelled) { + console.warn("Failed to get webcam access:", cameraError); + setWebcamEnabledState(false); + toast.error(t("recording.cameraBlocked")); + } + } + }; + + void acquire(); + + return () => { + cancelled = true; + if (acquiredStream) { + acquiredStream.getTracks().forEach((track) => track.stop()); + webcamStream.current = null; + } + }; + }, [webcamEnabled, webcamDeviceId, t]); + const finalizeRecording = useCallback( ( activeScreenRecorder: RecorderHandle, @@ -408,32 +464,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } - if (webcamEnabled) { - try { - webcamStream.current = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: webcamDeviceId - ? { - deviceId: { exact: webcamDeviceId }, - width: { ideal: WEBCAM_TARGET_WIDTH }, - height: { ideal: WEBCAM_TARGET_HEIGHT }, - frameRate: { ideal: WEBCAM_TARGET_FRAME_RATE, max: WEBCAM_TARGET_FRAME_RATE }, - } - : { - width: { ideal: WEBCAM_TARGET_WIDTH }, - height: { ideal: WEBCAM_TARGET_HEIGHT }, - frameRate: { ideal: WEBCAM_TARGET_FRAME_RATE, max: WEBCAM_TARGET_FRAME_RATE }, - }, - }); - } catch (cameraError) { - console.warn("Failed to get webcam access:", cameraError); - if (webcamStream.current) { - webcamStream.current.getTracks().forEach((track) => track.stop()); - webcamStream.current = null; - } - setWebcamEnabledState(false); - toast.error(t("recording.cameraDenied")); - } + if (webcamEnabled && !webcamStream.current) { + setWebcamEnabledState(false); + toast.error(t("recording.cameraDenied")); } stream.current = new MediaStream(); diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index 6fdc310..8acd181 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -30,6 +30,7 @@ "systemAudioUnavailable": "System audio not available. Recording without system audio.", "microphoneDenied": "Microphone access denied. Recording will continue without audio.", "cameraDenied": "Camera access denied. Recording will continue without webcam.", + "cameraDisconnected": "Webcam disconnected.", "permissionDenied": "Recording permission denied. Please allow screen recording." } } diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index 99adc78..5834622 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -30,6 +30,7 @@ "systemAudioUnavailable": "Audio del sistema no disponible. Grabando sin audio del sistema.", "microphoneDenied": "Acceso al micrófono denegado. La grabación continuará sin audio.", "cameraDenied": "Acceso a la cámara denegado. La grabación continuará sin cámara web.", + "cameraDisconnected": "Cámara web desconectada.", "permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla." } } diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 5d27bef..0723c88 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -30,6 +30,7 @@ "systemAudioUnavailable": "系统音频不可用。将在无系统音频的情况下录制。", "microphoneDenied": "麦克风权限被拒绝。录制将继续,但不包含音频。", "cameraDenied": "摄像头权限被拒绝。录制将继续,但不包含摄像头画面。", + "cameraDisconnected": "摄像头已断开连接。", "permissionDenied": "录屏权限被拒绝。请允许屏幕录制。" } } diff --git a/src/lib/requestCameraAccess.ts b/src/lib/requestCameraAccess.ts index 2494224..cfcd4d1 100644 --- a/src/lib/requestCameraAccess.ts +++ b/src/lib/requestCameraAccess.ts @@ -17,9 +17,7 @@ export async function requestCameraAccess(): Promise { if (window.electronAPI?.requestCameraAccess) { try { const electronResult = await window.electronAPI.requestCameraAccess(); - if (!electronResult.success || !electronResult.granted) { - return electronResult; - } + return electronResult; } catch (error) { return { success: false, From 954b99e962dd2c820ddc7f4250bc78b722881500 Mon Sep 17 00:00:00 2001 From: dheerajmr01 Date: Sat, 4 Apr 2026 12:12:00 -0500 Subject: [PATCH 004/152] fix: addresses review - differentiate webcam error types and handle stream acquisition --- .gitignore | 1 + src/hooks/useScreenRecorder.ts | 38 ++++++++++++++++++++++++++---- src/i18n/locales/en/editor.json | 1 + src/i18n/locales/es/editor.json | 1 + src/i18n/locales/zh-CN/editor.json | 1 + 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 70cc387..199ff44 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ dist-ssr *.sw? release/** *.kiro/ +.claude/ # npx electron-builder --mac --win # Playwright diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index ba95e60..8448b82 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -103,6 +103,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const allowAutoFinalize = useRef(false); const discardRecordingId = useRef(null); const restarting = useRef(false); + const webcamReady = useRef(false); const selectMimeType = () => { const preferred = [ @@ -182,6 +183,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { let cancelled = false; let acquiredStream: MediaStream | null = null; + webcamReady.current = false; const acquire = async () => { try { @@ -217,11 +219,21 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; }); webcamStream.current = stream; + webcamReady.current = true; } catch (cameraError) { if (!cancelled) { console.warn("Failed to get webcam access:", cameraError); setWebcamEnabledState(false); - toast.error(t("recording.cameraBlocked")); + const isDeviceError = + cameraError instanceof DOMException && + [ + "NotFoundError", + "DevicesNotFoundError", + "OverconstrainedError", + "NotReadableError", + ].includes(cameraError.name); + toast.error(t(isDeviceError ? "recording.cameraNotFound" : "recording.cameraBlocked")); + webcamReady.current = true; } } }; @@ -230,6 +242,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return () => { cancelled = true; + webcamReady.current = false; if (acquiredStream) { acquiredStream.getTracks().forEach((track) => track.stop()); webcamStream.current = null; @@ -464,9 +477,26 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } - if (webcamEnabled && !webcamStream.current) { - setWebcamEnabledState(false); - toast.error(t("recording.cameraDenied")); + if (webcamEnabled) { + if (!webcamReady.current) { + await new Promise((resolve) => { + const interval = setInterval(() => { + if (webcamReady.current) { + clearInterval(interval); + resolve(); + } + }, 50); + setTimeout(() => { + clearInterval(interval); + resolve(); + }, 5000); + }); + } + if (!webcamStream.current) { + // The useEffect already showed the appropriate error toast + // (cameraNotFound or cameraBlocked), so just disable the state. + setWebcamEnabledState(false); + } } stream.current = new MediaStream(); diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index 8acd181..e0cf4fd 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -31,6 +31,7 @@ "microphoneDenied": "Microphone access denied. Recording will continue without audio.", "cameraDenied": "Camera access denied. Recording will continue without webcam.", "cameraDisconnected": "Webcam disconnected.", + "cameraNotFound": "Camera not found.", "permissionDenied": "Recording permission denied. Please allow screen recording." } } diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index 5834622..7956b75 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -31,6 +31,7 @@ "microphoneDenied": "Acceso al micrófono denegado. La grabación continuará sin audio.", "cameraDenied": "Acceso a la cámara denegado. La grabación continuará sin cámara web.", "cameraDisconnected": "Cámara web desconectada.", + "cameraNotFound": "Cámara no encontrada.", "permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla." } } diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 0723c88..2360fe8 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -31,6 +31,7 @@ "microphoneDenied": "麦克风权限被拒绝。录制将继续,但不包含音频。", "cameraDenied": "摄像头权限被拒绝。录制将继续,但不包含摄像头画面。", "cameraDisconnected": "摄像头已断开连接。", + "cameraNotFound": "未找到摄像头。", "permissionDenied": "录屏权限被拒绝。请允许屏幕录制。" } } From b270affb25083e534025d34bbec7977d0ce0d8de Mon Sep 17 00:00:00 2001 From: dheerajmr01 Date: Sat, 4 Apr 2026 12:42:23 -0500 Subject: [PATCH 005/152] trigger re-review From 5ff613922ffa5fb1aa864214698d6f1c9cbedbb0 Mon Sep 17 00:00:00 2001 From: dheerajmr01 Date: Sat, 4 Apr 2026 14:03:26 -0500 Subject: [PATCH 006/152] fix:addresses comments - clear track.onended before intentional stop to prevent disconnect toast --- src/hooks/useScreenRecorder.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 8448b82..5564716 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -204,7 +204,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }); if (cancelled) { - stream.getTracks().forEach((track) => track.stop()); + stream.getTracks().forEach((track) => { + track.onended = null; + track.stop(); + }); return; } @@ -244,7 +247,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { cancelled = true; webcamReady.current = false; if (acquiredStream) { - acquiredStream.getTracks().forEach((track) => track.stop()); + acquiredStream.getTracks().forEach((track) => { + track.onended = null; + track.stop(); + }); webcamStream.current = null; } }; @@ -493,8 +499,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }); } if (!webcamStream.current) { - // The useEffect already showed the appropriate error toast - // (cameraNotFound or cameraBlocked), so just disable the state. setWebcamEnabledState(false); } } From 210baee0dac278a6f9e824dc5aeefacfa2e86494 Mon Sep 17 00:00:00 2001 From: dheerajmr01 Date: Sat, 4 Apr 2026 14:25:48 -0500 Subject: [PATCH 007/152] added acquireId guard to prevent stale getUserMedia from repopulating webcamStream --- src/hooks/useScreenRecorder.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 5564716..f4a624c 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -104,6 +104,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const discardRecordingId = useRef(null); const restarting = useRef(false); const webcamReady = useRef(false); + const webcamAcquireId = useRef(0); const selectMimeType = () => { const preferred = [ @@ -183,6 +184,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { let cancelled = false; let acquiredStream: MediaStream | null = null; + const thisAcquireId = ++webcamAcquireId.current; webcamReady.current = false; const acquire = async () => { @@ -203,7 +205,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }, }); - if (cancelled) { + if (cancelled || thisAcquireId !== webcamAcquireId.current) { stream.getTracks().forEach((track) => { track.onended = null; track.stop(); @@ -499,6 +501,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }); } if (!webcamStream.current) { + webcamAcquireId.current++; setWebcamEnabledState(false); } } From 5426b6284cde53de963ede211650ccb8a1260638 Mon Sep 17 00:00:00 2001 From: cocoon Date: Sun, 5 Apr 2026 09:16:04 +0000 Subject: [PATCH 008/152] feat(editor): duplicate annotations --- .../video-editor/AnnotationSettingsPanel.tsx | 34 ++++++++++++++----- src/components/video-editor/SettingsPanel.tsx | 3 ++ src/components/video-editor/VideoEditor.tsx | 28 +++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index b289392..db3197f 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,6 +1,7 @@ import Block from "@uiw/react-color-block"; import { AlignCenter, + Copy, AlignLeft, AlignRight, Bold, @@ -40,6 +41,7 @@ interface AnnotationSettingsPanelProps { onTypeChange: (type: AnnotationType) => void; onStyleChange: (style: Partial) => void; onFigureDataChange?: (figureData: FigureData) => void; + onDuplicate?: () => void; onDelete: () => void; } @@ -62,6 +64,7 @@ export function AnnotationSettingsPanel({ onTypeChange, onStyleChange, onFigureDataChange, + onDuplicate, onDelete, }: AnnotationSettingsPanelProps) { const t = useScopedT("settings"); @@ -597,15 +600,28 @@ export function AnnotationSettingsPanel({ - +
+ + + +
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 7e556b8..96a61af 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -140,6 +140,7 @@ interface SettingsPanelProps { onAnnotationTypeChange?: (id: string, type: AnnotationType) => void; onAnnotationStyleChange?: (id: string, style: Partial) => void; onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void; + onAnnotationDuplicate?: (id: string) => void; onAnnotationDelete?: (id: string) => void; selectedSpeedId?: string | null; selectedSpeedValue?: PlaybackSpeed | null; @@ -213,6 +214,7 @@ export function SettingsPanel({ onAnnotationTypeChange, onAnnotationStyleChange, onAnnotationFigureDataChange, + onAnnotationDuplicate, onAnnotationDelete, selectedSpeedId, selectedSpeedValue, @@ -466,6 +468,7 @@ export function SettingsPanel({ ? (figureData) => onAnnotationFigureDataChange(selectedAnnotation.id, figureData) : undefined } + onDuplicate={onAnnotationDuplicate ? () => onAnnotationDuplicate(selectedAnnotation.id) : undefined} onDelete={() => onAnnotationDelete(selectedAnnotation.id)} /> ); diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 4e5e978..f489e2d 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -831,6 +831,33 @@ export default function VideoEditor() { [pushState], ); + const handleAnnotationDuplicate = useCallback( + (id: string) => { + const duplicateId = `annotation-${nextAnnotationIdRef.current++}`; + const duplicateZIndex = nextAnnotationZIndexRef.current++; + pushState((prev) => { + const source = prev.annotationRegions.find((region) => region.id === id); + if (!source) return {}; + + const duplicate: AnnotationRegion = { + ...source, + id: duplicateId, + zIndex: duplicateZIndex, + position: { x: source.position.x + 4, y: source.position.y + 4 }, + size: { ...source.size }, + style: { ...source.style }, + figureData: source.figureData ? { ...source.figureData } : undefined, + }; + + return { annotationRegions: [...prev.annotationRegions, duplicate] }; + }); + setSelectedAnnotationId(duplicateId); + setSelectedZoomId(null); + setSelectedTrimId(null); + }, + [pushState], + ); + const handleAnnotationDelete = useCallback( (id: string) => { pushState((prev) => ({ @@ -1680,6 +1707,7 @@ export default function VideoEditor() { onAnnotationTypeChange={handleAnnotationTypeChange} onAnnotationStyleChange={handleAnnotationStyleChange} onAnnotationFigureDataChange={handleAnnotationFigureDataChange} + onAnnotationDuplicate={handleAnnotationDuplicate} onAnnotationDelete={handleAnnotationDelete} selectedSpeedId={selectedSpeedId} selectedSpeedValue={ From 36453d740fa1c96c60132b1c8c19e2a5f8f32cfa Mon Sep 17 00:00:00 2001 From: imAaryash Date: Mon, 6 Apr 2026 08:18:40 +0530 Subject: [PATCH 009/152] Update LaunchWindow.tsx --- src/components/launch/LaunchWindow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 249dd77..0cfef9e 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -228,7 +228,7 @@ export function LaunchWindow() { }; return ( -
+
{/* Language switcher — top-left, beside traffic lights */}
Date: Mon, 6 Apr 2026 09:41:42 +0530 Subject: [PATCH 010/152] feat(launch): refine recording HUD and language switching UX --- src/components/launch/LaunchWindow.tsx | 278 ++++++++++++++++--------- src/components/ui/select.tsx | 64 +++--- src/contexts/I18nContext.tsx | 102 ++++++++- src/i18n/locales/en/launch.json | 8 +- src/i18n/locales/es/launch.json | 8 +- src/i18n/locales/zh-CN/launch.json | 8 +- 6 files changed, 341 insertions(+), 127 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 249dd77..79a32d5 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1,5 +1,5 @@ import { ChevronDown, Languages } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs"; import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; @@ -18,9 +18,8 @@ import { } from "react-icons/md"; import { RxDragHandleDots2 } from "react-icons/rx"; import { useI18n, useScopedT } from "@/contexts/I18nContext"; -import { type Locale, SUPPORTED_LOCALES } from "@/i18n/config"; +import { SUPPORTED_LOCALES } from "@/i18n/config"; import { getLocaleName } from "@/i18n/loader"; -import { isMac as getIsMac } from "@/utils/platformUtils"; import { useAudioLevelMeter } from "../../hooks/useAudioLevelMeter"; import { useCameraDevices } from "../../hooks/useCameraDevices"; import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices"; @@ -28,6 +27,7 @@ import { useScreenRecorder } from "../../hooks/useScreenRecorder"; import { requestCameraAccess } from "../../lib/requestCameraAccess"; import { formatTimePadded } from "../../utils/timeUtils"; import { AudioLevelMeter } from "../ui/audio-level-meter"; +import { Button } from "../ui/button"; import { Tooltip } from "../ui/tooltip"; import styles from "./LaunchWindow.module.css"; @@ -67,17 +67,24 @@ const hudGroupClasses = const hudIconBtnClasses = "flex items-center justify-center p-2 rounded-full transition-all duration-150 cursor-pointer text-white hover:bg-white/10 hover:scale-[1.08] active:scale-95"; +const hudAuxIconBtnClasses = + "flex items-center justify-center p-1.5 rounded-full transition-colors duration-150 text-white/55 hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed"; + const windowBtnClasses = "flex items-center justify-center p-2 rounded-full transition-all duration-150 cursor-pointer opacity-50 hover:opacity-90 hover:bg-white/[0.08]"; +const hudSidebarClasses = "ml-0.5 pl-1.5 border-l border-white/10 flex items-center gap-0.5"; + export function LaunchWindow() { const t = useScopedT("launch"); - const { locale, setLocale } = useI18n(); - const [isMac, setIsMac] = useState(false); - - useEffect(() => { - getIsMac().then(setIsMac); - }, []); + const { + locale, + setLocale, + systemLocaleSuggestion, + acceptSystemLocaleSuggestion, + dismissSystemLocaleSuggestion, + } = useI18n(); + const suggestedLanguageName = systemLocaleSuggestion ? getLocaleName(systemLocaleSuggestion) : ""; const { recording, @@ -164,6 +171,8 @@ export function LaunchWindow() { const [selectedSource, setSelectedSource] = useState("Screen"); const [hasSelectedSource, setHasSelectedSource] = useState(false); + const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); + const languageMenuRef = useRef(null); useEffect(() => { const checkSelectedSource = async () => { @@ -185,6 +194,31 @@ export function LaunchWindow() { return () => clearInterval(interval); }, []); + useEffect(() => { + if (!isLanguageMenuOpen) return; + + const onPointerDown = (event: MouseEvent) => { + if (!languageMenuRef.current) return; + if (!languageMenuRef.current.contains(event.target as Node)) { + setIsLanguageMenuOpen(false); + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsLanguageMenuOpen(false); + } + }; + + document.addEventListener("mousedown", onPointerDown); + document.addEventListener("keydown", onKeyDown); + + return () => { + document.removeEventListener("mousedown", onPointerDown); + document.removeEventListener("keydown", onKeyDown); + }; + }, [isLanguageMenuOpen]); + const openSourceSelector = () => { if (window.electronAPI) { window.electronAPI.openSourceSelector(); @@ -228,25 +262,42 @@ export function LaunchWindow() { }; return ( -
- {/* Language switcher — top-left, beside traffic lights */} -
- - -
+
+ {t("systemLanguagePrompt.title")} +
+
+ {t("systemLanguagePrompt.description", { + language: suggestedLanguageName, + })} +
+
+ + +
+
+ )} {/* Device selectors — fixed above HUD bar, viewport-relative, never clipped */} {(showMicControls || showWebcamControls) && ( @@ -433,104 +484,133 @@ export function LaunchWindow() { {/* Record/Stop group */} {recording && ( - - - + + + + + + + + +
)} - {/* Restart recording */} - {recording && ( - - - + {!recording && ( + <> + {/* Open video file */} + + + + + {/* Open project */} + + + + )} - {/* Cancel recording */} - {recording && ( - + {/* Right sidebar controls */} +
+
- - )} - {/* Open video file */} - - - + {isLanguageMenuOpen && ( +
+ {SUPPORTED_LOCALES.map((loc) => ( + + ))} +
+ )} +
- {/* Open project */} - - - - - {/* Window controls */} -
- - + {/* Window controls */} +
+ + +
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 53e21e6..3326ee9 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -62,34 +62,50 @@ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayNam const SelectContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( - - - - & { + showScrollButtons?: boolean; + viewportClassName?: string; + } +>( + ( + { + className, + children, + position = "popper", + showScrollButtons = true, + viewportClassName, + ...props + }, + ref, + ) => ( + + - {children} - - - - -)); + {showScrollButtons ? : null} + + {children} + + {showScrollButtons ? : null} + + + ), +); SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index 0b75212..405d5c3 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -22,8 +22,13 @@ interface I18nContextValue { locale: Locale; setLocale: (locale: Locale) => void; t: (qualifiedKey: string, vars?: TranslateVars) => string; + systemLocaleSuggestion: Locale | null; + acceptSystemLocaleSuggestion: () => void; + dismissSystemLocaleSuggestion: () => void; } +const SYSTEM_LANGUAGE_PROMPT_SEEN_KEY = "openscreen-system-language-prompt-seen"; + const I18nContext = createContext(null); export function useI18n(): I18nContextValue { @@ -44,6 +49,35 @@ function isSupportedLocale(value: string): value is Locale { return (SUPPORTED_LOCALES as readonly string[]).includes(value); } +function getSupportedSystemLocale(): Locale | null { + if (typeof navigator === "undefined") return null; + + const candidates = + Array.isArray(navigator.languages) && navigator.languages.length > 0 + ? navigator.languages + : [navigator.language]; + + for (const candidate of candidates) { + if (!candidate) continue; + if (isSupportedLocale(candidate)) return candidate; + + const exactMatch = SUPPORTED_LOCALES.find( + (locale) => locale.toLowerCase() === candidate.toLowerCase(), + ); + if (exactMatch) return exactMatch; + + const baseLanguage = candidate.split("-")[0]?.toLowerCase(); + if (!baseLanguage) continue; + + if (baseLanguage === "zh") return "zh-CN"; + + const baseMatch = SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === baseLanguage); + if (baseMatch) return baseMatch; + } + + return null; +} + function getInitialLocale(): Locale { try { const stored = localStorage.getItem(LOCALE_STORAGE_KEY); @@ -56,6 +90,15 @@ function getInitialLocale(): Locale { export function I18nProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(getInitialLocale); + const [systemLocaleSuggestion, setSystemLocaleSuggestion] = useState(null); + + const markPromptAsHandled = useCallback(() => { + try { + localStorage.setItem(SYSTEM_LANGUAGE_PROMPT_SEEN_KEY, "1"); + } catch { + // localStorage may be unavailable + } + }, []); const setLocale = useCallback((newLocale: Locale) => { setLocaleState(newLocale); @@ -73,6 +116,46 @@ export function I18nProvider({ children }: { children: ReactNode }) { document.documentElement.lang = locale; }, [locale]); + useEffect(() => { + let hasStoredLocale = false; + let hasHandledSystemPrompt = false; + try { + const stored = localStorage.getItem(LOCALE_STORAGE_KEY); + hasStoredLocale = Boolean(stored && isSupportedLocale(stored)); + hasHandledSystemPrompt = localStorage.getItem(SYSTEM_LANGUAGE_PROMPT_SEEN_KEY) === "1"; + } catch { + // localStorage may be unavailable + } + + if (hasStoredLocale || hasHandledSystemPrompt || systemLocaleSuggestion) return; + + const detectedSystemLocale = getSupportedSystemLocale(); + if (!detectedSystemLocale || detectedSystemLocale === DEFAULT_LOCALE) { + markPromptAsHandled(); + return; + } + + setSystemLocaleSuggestion(detectedSystemLocale); + }, [markPromptAsHandled, systemLocaleSuggestion]); + + const acceptSystemLocaleSuggestion = useCallback(() => { + if (!systemLocaleSuggestion) return; + setLocale(systemLocaleSuggestion); + setSystemLocaleSuggestion(null); + markPromptAsHandled(); + }, [markPromptAsHandled, setLocale, systemLocaleSuggestion]); + + const dismissSystemLocaleSuggestion = useCallback(() => { + setSystemLocaleSuggestion(null); + try { + // Persisting default locale avoids showing this prompt again. + localStorage.setItem(LOCALE_STORAGE_KEY, DEFAULT_LOCALE); + } catch { + // localStorage may be unavailable + } + markPromptAsHandled(); + }, [markPromptAsHandled]); + const t = useCallback( (qualifiedKey: string, vars?: TranslateVars): string => { const dotIndex = qualifiedKey.indexOf("."); @@ -84,7 +167,24 @@ export function I18nProvider({ children }: { children: ReactNode }) { [locale], ); - const value = useMemo(() => ({ locale, setLocale, t }), [locale, setLocale, t]); + const value = useMemo( + () => ({ + locale, + setLocale, + t, + systemLocaleSuggestion, + acceptSystemLocaleSuggestion, + dismissSystemLocaleSuggestion, + }), + [ + locale, + setLocale, + t, + systemLocaleSuggestion, + acceptSystemLocaleSuggestion, + dismissSystemLocaleSuggestion, + ], + ); return {children}; } diff --git a/src/i18n/locales/en/launch.json b/src/i18n/locales/en/launch.json index cf111c4..e959a54 100644 --- a/src/i18n/locales/en/launch.json +++ b/src/i18n/locales/en/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "Please select a source to record" }, - "language": "Language" + "language": "Language", + "systemLanguagePrompt": { + "title": "Use your system language?", + "description": "We detected {{language}} as your system language. Do you want to switch OpenScreen to {{language}}?", + "switch": "Switch to {{language}}", + "keepDefault": "Keep current language" + } } diff --git a/src/i18n/locales/es/launch.json b/src/i18n/locales/es/launch.json index f47bc81..68919aa 100644 --- a/src/i18n/locales/es/launch.json +++ b/src/i18n/locales/es/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "Por favor selecciona una fuente para grabar" }, - "language": "Idioma" + "language": "Idioma", + "systemLanguagePrompt": { + "title": "¿Usar el idioma del sistema?", + "description": "Detectamos {{language}} como idioma de tu sistema. ¿Quieres cambiar OpenScreen a {{language}}?", + "switch": "Cambiar a {{language}}", + "keepDefault": "Mantener idioma actual" + } } diff --git a/src/i18n/locales/zh-CN/launch.json b/src/i18n/locales/zh-CN/launch.json index 6b63df1..a5c2a9d 100644 --- a/src/i18n/locales/zh-CN/launch.json +++ b/src/i18n/locales/zh-CN/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "请选择要录制的源" }, - "language": "语言" + "language": "语言", + "systemLanguagePrompt": { + "title": "使用系统语言吗?", + "description": "我们检测到你的系统语言是{{language}}。是否将 OpenScreen 切换为{{language}}?", + "switch": "切换到{{language}}", + "keepDefault": "保持当前语言" + } } From 4e43b59b42fbe52690eb0267a3fe2b0980461bd3 Mon Sep 17 00:00:00 2001 From: imAaryash Date: Mon, 6 Apr 2026 10:11:07 +0530 Subject: [PATCH 011/152] fix(launch): polish language menu behavior --- src/components/launch/LaunchWindow.tsx | 103 ++++++++++--------------- src/contexts/I18nContext.tsx | 9 ++- 2 files changed, 46 insertions(+), 66 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 79a32d5..a430be0 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1,5 +1,5 @@ -import { ChevronDown, Languages } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { Check, ChevronDown, Languages } from "lucide-react"; +import { useEffect, useState } from "react"; import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs"; import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; @@ -28,6 +28,12 @@ import { requestCameraAccess } from "../../lib/requestCameraAccess"; import { formatTimePadded } from "../../utils/timeUtils"; import { AudioLevelMeter } from "../ui/audio-level-meter"; import { Button } from "../ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; import { Tooltip } from "../ui/tooltip"; import styles from "./LaunchWindow.module.css"; @@ -171,8 +177,6 @@ export function LaunchWindow() { const [selectedSource, setSelectedSource] = useState("Screen"); const [hasSelectedSource, setHasSelectedSource] = useState(false); - const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); - const languageMenuRef = useRef(null); useEffect(() => { const checkSelectedSource = async () => { @@ -194,31 +198,6 @@ export function LaunchWindow() { return () => clearInterval(interval); }, []); - useEffect(() => { - if (!isLanguageMenuOpen) return; - - const onPointerDown = (event: MouseEvent) => { - if (!languageMenuRef.current) return; - if (!languageMenuRef.current.contains(event.target as Node)) { - setIsLanguageMenuOpen(false); - } - }; - - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setIsLanguageMenuOpen(false); - } - }; - - document.addEventListener("mousedown", onPointerDown); - document.addEventListener("keydown", onKeyDown); - - return () => { - document.removeEventListener("mousedown", onPointerDown); - document.removeEventListener("keydown", onKeyDown); - }; - }, [isLanguageMenuOpen]); - const openSourceSelector = () => { if (window.electronAPI) { window.electronAPI.openSourceSelector(); @@ -557,42 +536,38 @@ export function LaunchWindow() { {/* Right sidebar controls */}
-
- - - {isLanguageMenuOpen && ( -
+ + - ))} -
- )} -
+
+ +
+ + + + + {SUPPORTED_LOCALES.map((loc) => ( + setLocale(loc)} + className={`flex items-center justify-between rounded-sm px-2 py-1.5 text-[11px] transition-colors ${loc === locale ? "text-white" : "text-white/90"} focus:bg-white/10 focus:text-white ${styles.electronNoDrag}`} + > + {getLocaleName(loc)} + {loc === locale ? : null} + + ))} + + {/* Window controls */}
diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index 405d5c3..f9c5ee5 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -5,6 +5,7 @@ import { useContext, useEffect, useMemo, + useRef, useState, } from "react"; import { @@ -91,6 +92,7 @@ function getInitialLocale(): Locale { export function I18nProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(getInitialLocale); const [systemLocaleSuggestion, setSystemLocaleSuggestion] = useState(null); + const hasRunSystemLocaleCheckRef = useRef(false); const markPromptAsHandled = useCallback(() => { try { @@ -117,6 +119,9 @@ export function I18nProvider({ children }: { children: ReactNode }) { }, [locale]); useEffect(() => { + if (hasRunSystemLocaleCheckRef.current) return; + hasRunSystemLocaleCheckRef.current = true; + let hasStoredLocale = false; let hasHandledSystemPrompt = false; try { @@ -127,7 +132,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { // localStorage may be unavailable } - if (hasStoredLocale || hasHandledSystemPrompt || systemLocaleSuggestion) return; + if (hasStoredLocale || hasHandledSystemPrompt) return; const detectedSystemLocale = getSupportedSystemLocale(); if (!detectedSystemLocale || detectedSystemLocale === DEFAULT_LOCALE) { @@ -136,7 +141,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { } setSystemLocaleSuggestion(detectedSystemLocale); - }, [markPromptAsHandled, systemLocaleSuggestion]); + }, [markPromptAsHandled]); const acceptSystemLocaleSuggestion = useCallback(() => { if (!systemLocaleSuggestion) return; From 3d20c67c63dfe7c7ad58c79bcb1242a971bf6da9 Mon Sep 17 00:00:00 2001 From: imAaryash Date: Mon, 6 Apr 2026 10:15:41 +0530 Subject: [PATCH 012/152] fix(i18n): resolve prompt persistence and language menu behavior --- src/components/launch/LaunchWindow.tsx | 8 ++++++-- src/contexts/I18nContext.tsx | 14 ++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index a430be0..137b28c 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -89,6 +89,7 @@ export function LaunchWindow() { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, } = useI18n(); const suggestedLanguageName = systemLocaleSuggestion ? getLocaleName(systemLocaleSuggestion) : ""; @@ -554,12 +555,15 @@ export function LaunchWindow() { side="top" sideOffset={6} collisionPadding={6} - className={`w-36 min-w-0 max-h-none overflow-hidden border-white/15 bg-[rgba(24,24,34,0.98)] p-1 text-white shadow-2xl backdrop-blur-xl ${styles.electronNoDrag}`} + className={`w-36 min-w-0 max-h-none overflow-y-hidden overflow-x-hidden border-white/15 bg-[rgba(24,24,34,0.98)] p-1 text-white shadow-2xl backdrop-blur-xl ${styles.electronNoDrag}`} > {SUPPORTED_LOCALES.map((loc) => ( setLocale(loc)} + onSelect={() => { + setLocale(loc); + resolveSystemLocaleSuggestion(); + }} className={`flex items-center justify-between rounded-sm px-2 py-1.5 text-[11px] transition-colors ${loc === locale ? "text-white" : "text-white/90"} focus:bg-white/10 focus:text-white ${styles.electronNoDrag}`} > {getLocaleName(loc)} diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index f9c5ee5..84640ea 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -26,6 +26,7 @@ interface I18nContextValue { systemLocaleSuggestion: Locale | null; acceptSystemLocaleSuggestion: () => void; dismissSystemLocaleSuggestion: () => void; + resolveSystemLocaleSuggestion: () => void; } const SYSTEM_LANGUAGE_PROMPT_SEEN_KEY = "openscreen-system-language-prompt-seen"; @@ -152,12 +153,11 @@ export function I18nProvider({ children }: { children: ReactNode }) { const dismissSystemLocaleSuggestion = useCallback(() => { setSystemLocaleSuggestion(null); - try { - // Persisting default locale avoids showing this prompt again. - localStorage.setItem(LOCALE_STORAGE_KEY, DEFAULT_LOCALE); - } catch { - // localStorage may be unavailable - } + markPromptAsHandled(); + }, [markPromptAsHandled]); + + const resolveSystemLocaleSuggestion = useCallback(() => { + setSystemLocaleSuggestion(null); markPromptAsHandled(); }, [markPromptAsHandled]); @@ -180,6 +180,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, }), [ locale, @@ -188,6 +189,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, ], ); From 112f02fe032de8a970cd9d022032bd959f5cbb9f Mon Sep 17 00:00:00 2001 From: moncef Date: Tue, 7 Apr 2026 00:30:23 +0100 Subject: [PATCH 013/152] feat: implement video editor timeline components with interactive zoom, trim, and speed region controls. --- src/components/video-editor/SettingsPanel.tsx | 47 ++++++++- src/components/video-editor/VideoEditor.tsx | 31 +++++- src/components/video-editor/timeline/Item.tsx | 99 ++++++++++++++++++- .../video-editor/timeline/TimelineEditor.tsx | 12 +++ src/components/video-editor/types.ts | 2 + .../videoPlayback/zoomRegionUtils.ts | 33 ++++--- src/i18n/locales/en/settings.json | 7 ++ src/i18n/locales/es/settings.json | 7 ++ src/i18n/locales/zh-CN/settings.json | 7 ++ 9 files changed, 230 insertions(+), 15 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 7e556b8..34adddd 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -150,6 +150,9 @@ interface SettingsPanelProps { onWebcamLayoutPresetChange?: (preset: WebcamLayoutPreset) => void; webcamMaskShape?: import("./types").WebcamMaskShape; onWebcamMaskShapeChange?: (shape: import("./types").WebcamMaskShape) => void; + selectedZoomInDuration?: number; + selectedZoomOutDuration?: number; + onZoomDurationChange?: (zoomIn: number, zoomOut: number) => void; } export default SettingsPanel; @@ -163,6 +166,14 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [ { depth: 6, label: "5×" }, ]; +// TODO: make this configurable +const ZOOM_SPEED_OPTIONS = [ + { label: "Instant", zoomIn: 0, zoomOut: 0 }, + { label: "Fast", zoomIn: 500, zoomOut: 350 }, + { label: "Smooth", zoomIn: 1522, zoomOut: 1015 }, + { label: "Lazy", zoomIn: 3000, zoomOut: 2000 }, +]; + export function SettingsPanel({ selected, onWallpaperChange, @@ -223,6 +234,9 @@ export function SettingsPanel({ onWebcamLayoutPresetChange, webcamMaskShape = "rectangle", onWebcamMaskShapeChange, + selectedZoomInDuration, + selectedZoomOutDuration, + onZoomDurationChange, }: SettingsPanelProps) { const t = useScopedT("settings"); const [wallpaperPaths, setWallpaperPaths] = useState([]); @@ -547,6 +561,37 @@ export function SettingsPanel({ )}
)} + + {zoomEnabled && ( +
+ + {t("zoom.speed.title") || "Zoom Speed"} + +
+ {ZOOM_SPEED_OPTIONS.map((opt) => { + const isActive = + selectedZoomInDuration === opt.zoomIn && + selectedZoomOutDuration === opt.zoomOut; + return ( + + ); + })} +
+
+ )} {zoomEnabled && (
diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index f265fe4..db9ae1a 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -1,5 +1,5 @@ import type { Span } from "dnd-timeline"; -import { useItem } from "dnd-timeline"; +import { useItem, useTimelineContext } from "dnd-timeline"; import { Gauge, MessageSquare, Scissors, ZoomIn } from "lucide-react"; import { useMemo } from "react"; import { cn } from "@/lib/utils"; @@ -13,8 +13,11 @@ interface ItemProps { isSelected?: boolean; onSelect?: () => void; zoomDepth?: number; + zoomInDurationMs?: number; + zoomOutDurationMs?: number; speedValue?: number; variant?: "zoom" | "trim" | "annotation" | "speed"; + onZoomDurationChange?: (id: string, zoomIn: number, zoomOut: number) => void; } // Map zoom depth to multiplier labels @@ -44,10 +47,14 @@ export default function Item({ isSelected = false, onSelect, zoomDepth = 1, + zoomInDurationMs, + zoomOutDurationMs, speedValue, variant = "zoom", children, + onZoomDurationChange, }: ItemProps) { + const { pixelsToValue } = useTimelineContext(); const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, span, @@ -101,6 +108,96 @@ export default function Item({ onSelect?.(); }} > + {isZoom && ( + <> + {/* Transition In Marker */} +
+ {/* Draggable handle for Transition In */} +
{ + e.stopPropagation(); + e.preventDefault(); + const target = e.currentTarget; + target.setPointerCapture(e.pointerId); + + const onPointerMove = (moveEvent: PointerEvent) => { + const deltaPx = moveEvent.clientX - e.clientX; + const deltaMs = pixelsToValue(deltaPx); + const newDuration = Math.max( + 0, + Math.min( + (zoomInDurationMs ?? 1522.575) + deltaMs, + span.end - span.start - (zoomOutDurationMs ?? 1015.05), + ), + ); + onZoomDurationChange?.(id, newDuration, zoomOutDurationMs ?? 1015.05); + }; + + const onPointerUp = () => { + target.releasePointerCapture(e.pointerId); + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", onPointerUp); + }; + + window.addEventListener("pointermove", onPointerMove); + window.addEventListener("pointerup", onPointerUp); + }} + /> + {/* Transition Out Marker */} +
+ {/* Draggable handle for Transition Out */} +
{ + e.stopPropagation(); + e.preventDefault(); + const target = e.currentTarget; + target.setPointerCapture(e.pointerId); + + const onPointerMove = (moveEvent: PointerEvent) => { + const deltaPx = e.clientX - moveEvent.clientX; // Inverted because right-anchored + const deltaMs = pixelsToValue(deltaPx); + const newDuration = Math.max( + 0, + Math.min( + (zoomOutDurationMs ?? 1015.05) + deltaMs, + span.end - span.start - (zoomInDurationMs ?? 1522.575), + ), + ); + onZoomDurationChange?.(id, zoomInDurationMs ?? 1522.575, newDuration); + }; + + const onPointerUp = () => { + target.releasePointerCapture(e.pointerId); + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", onPointerUp); + }; + + window.addEventListener("pointermove", onPointerMove); + window.addEventListener("pointerup", onPointerUp); + }} + /> + + )}
void; onZoomSuggested?: (span: Span, focus: ZoomFocus) => void; onZoomSpanChange: (id: string, span: Span) => void; + onZoomDurationChange: (id: string, zoomIn: number, zoomOut: number) => void; onZoomDelete: (id: string) => void; selectedZoomId: string | null; onSelectZoom: (id: string | null) => void; @@ -96,6 +97,8 @@ interface TimelineRenderItem { label: string; zoomDepth?: number; speedValue?: number; + zoomInDurationMs?: number; + zoomOutDurationMs?: number; variant: "zoom" | "trim" | "annotation" | "speed"; } @@ -530,6 +533,7 @@ function Timeline({ selectedTrimId, selectedAnnotationId, selectedSpeedId, + onZoomDurationChange, keyframes = [], }: { items: TimelineRenderItem[]; @@ -545,6 +549,7 @@ function Timeline({ selectedTrimId?: string | null; selectedAnnotationId?: string | null; selectedSpeedId?: string | null; + onZoomDurationChange: (id: string, zoomIn: number, zoomOut: number) => void; keyframes?: { id: string; time: number }[]; }) { const t = useScopedT("timeline"); @@ -668,6 +673,9 @@ function Timeline({ isSelected={item.id === selectedZoomId} onSelect={() => onSelectZoom?.(item.id)} zoomDepth={item.zoomDepth} + zoomInDurationMs={item.zoomInDurationMs} + zoomOutDurationMs={item.zoomOutDurationMs} + onZoomDurationChange={onZoomDurationChange} variant="zoom" > {item.label} @@ -740,6 +748,7 @@ export default function TimelineEditor({ onZoomAdded, onZoomSuggested, onZoomSpanChange, + onZoomDurationChange, onZoomDelete, selectedZoomId, onSelectZoom, @@ -1271,6 +1280,8 @@ export default function TimelineEditor({ span: { start: region.startMs, end: region.endMs }, label: t("labels.zoomItem", { index: String(index + 1) }), zoomDepth: region.depth, + zoomInDurationMs: region.zoomInDurationMs, + zoomOutDurationMs: region.zoomOutDurationMs, variant: "zoom", })); @@ -1494,6 +1505,7 @@ export default function TimelineEditor({ selectedTrimId={selectedTrimId} selectedAnnotationId={selectedAnnotationId} selectedSpeedId={selectedSpeedId} + onZoomDurationChange={onZoomDurationChange} keyframes={keyframes} /> diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index de06ba1..abad188 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -29,6 +29,8 @@ export interface ZoomRegion { depth: ZoomDepth; focus: ZoomFocus; focusMode?: ZoomFocusMode; + zoomInDurationMs?: number; + zoomOutDurationMs?: number; } export interface CursorTelemetryPoint { diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts index e5c16e1..12acdbf 100644 --- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts +++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts @@ -7,7 +7,6 @@ import { clamp01, cubicBezier, easeOutScreenStudio } from "./mathUtils"; const CHAINED_ZOOM_PAN_GAP_MS = 1500; const CONNECTED_ZOOM_PAN_DURATION_MS = 1000; -const ZOOM_IN_OVERLAP_MS = 500; type DominantRegionOptions = { connectZooms?: boolean; @@ -38,26 +37,36 @@ function easeConnectedPan(value: number) { return cubicBezier(0.1, 0.0, 0.2, 1.0, value); } -export function computeRegionStrength(region: ZoomRegion, timeMs: number) { - const zoomInEnd = region.startMs + ZOOM_IN_OVERLAP_MS; - const leadInStart = zoomInEnd - ZOOM_IN_TRANSITION_WINDOW_MS; - const leadOutEnd = region.endMs + TRANSITION_WINDOW_MS; +const DEFAULT_ZOOM_OUT_MS = TRANSITION_WINDOW_MS; +const DEFAULT_ZOOM_IN_MS = ZOOM_IN_TRANSITION_WINDOW_MS; - if (timeMs < leadInStart || timeMs > leadOutEnd) { +function getDurations(region: ZoomRegion) { + const zoomIn = region.zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; + const zoomOut = region.zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; + return { zoomIn, zoomOut }; +} + +export function computeRegionStrength(region: ZoomRegion, timeMs: number) { + const { zoomIn, zoomOut } = getDurations(region); + + if (timeMs < region.startMs || timeMs > region.endMs) { return 0; } - if (timeMs < zoomInEnd) { - const progress = (timeMs - leadInStart) / ZOOM_IN_TRANSITION_WINDOW_MS; + // Zooming in + if (timeMs < region.startMs + zoomIn) { + const progress = Math.max(0, Math.min(1, (timeMs - region.startMs) / zoomIn)); return easeOutScreenStudio(progress); } - if (timeMs <= region.endMs) { - return 1; + // Zooming out + if (timeMs > region.endMs - zoomOut) { + const progress = Math.max(0, Math.min(1, (region.endMs - timeMs) / zoomOut)); + return easeOutScreenStudio(progress); } - const progress = clamp01((timeMs - region.endMs) / TRANSITION_WINDOW_MS); - return 1 - easeOutScreenStudio(progress); + // Full zoom + return 1; } function getLinearFocus(start: ZoomFocus, end: ZoomFocus, amount: number): ZoomFocus { diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 632a569..44ac16a 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -8,6 +8,13 @@ "manual": "Manual", "auto": "Auto", "autoDescription": "Camera follows the recorded cursor position" + }, + "speed": { + "title": "Zoom Speed", + "instant": "Instant", + "fast": "Fast", + "smooth": "Smooth", + "lazy": "Lazy" } }, "speed": { diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 586e840..9f70ee4 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -8,6 +8,13 @@ "manual": "Manual", "auto": "Auto", "autoDescription": "La cámara sigue la posición del cursor grabado" + }, + "speed": { + "title": "Velocidad de zoom", + "instant": "Instantáneo", + "fast": "Rápido", + "smooth": "Suave", + "lazy": "Lento" } }, "speed": { diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index ab0d41b..2e69b80 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -8,6 +8,13 @@ "manual": "手动", "auto": "自动", "autoDescription": "摄像头跟随录制时的光标位置" + }, + "speed": { + "title": "缩放速度", + "instant": "即时", + "fast": "快速", + "smooth": "平滑", + "lazy": "缓慢" } }, "speed": { From 8f35cf090c8fdbc06db62c247771fe4790c1fa8d Mon Sep 17 00:00:00 2001 From: moncef Date: Tue, 7 Apr 2026 11:40:39 +0100 Subject: [PATCH 014/152] feat: add zoomRegionUtils to calculate dominant zoom regions and handle smooth transitions between connected regions --- .../video-editor/videoPlayback/zoomRegionUtils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts index 12acdbf..e9fd603 100644 --- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts +++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts @@ -41,8 +41,16 @@ const DEFAULT_ZOOM_OUT_MS = TRANSITION_WINDOW_MS; const DEFAULT_ZOOM_IN_MS = ZOOM_IN_TRANSITION_WINDOW_MS; function getDurations(region: ZoomRegion) { - const zoomIn = region.zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; - const zoomOut = region.zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; + let zoomIn = region.zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; + let zoomOut = region.zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; + + const duration = region.endMs - region.startMs; + if (zoomIn + zoomOut > duration) { + const scale = duration / (zoomIn + zoomOut); + zoomIn *= scale; + zoomOut *= scale; + } + return { zoomIn, zoomOut }; } From 7409631207f6c58a21e98a37c34dbb5105bc2d97 Mon Sep 17 00:00:00 2001 From: moncef Date: Tue, 7 Apr 2026 11:43:20 +0100 Subject: [PATCH 015/152] Fix pr review SelecedSpeedId --- src/components/video-editor/VideoEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 79ac03a..e4de186 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -1760,6 +1760,7 @@ export default function VideoEditor() { onAnnotationStyleChange={handleAnnotationStyleChange} onAnnotationFigureDataChange={handleAnnotationFigureDataChange} onAnnotationDelete={handleAnnotationDelete} + selectedSpeedId={selectedSpeedId} selectedSpeedValue={ selectedSpeedId ? (speedRegions.find((r) => r.id === selectedSpeedId)?.speed ?? null) From 0cb298d20bd7a53672dc550298ba9014c80c0e3c Mon Sep 17 00:00:00 2001 From: moncef Date: Tue, 7 Apr 2026 11:58:45 +0100 Subject: [PATCH 016/152] Fix Pr reviews --- src/components/video-editor/SettingsPanel.tsx | 9 ++-- src/components/video-editor/VideoEditor.tsx | 4 +- src/components/video-editor/timeline/Item.tsx | 49 +++++++++++++------ .../videoPlayback/zoomRegionUtils.ts | 11 +++-- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 34adddd..e45f439 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -166,7 +166,6 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [ { depth: 6, label: "5×" }, ]; -// TODO: make this configurable const ZOOM_SPEED_OPTIONS = [ { label: "Instant", zoomIn: 0, zoomOut: 0 }, { label: "Fast", zoomIn: 500, zoomOut: 350 }, @@ -570,8 +569,10 @@ export function SettingsPanel({
{ZOOM_SPEED_OPTIONS.map((opt) => { const isActive = - selectedZoomInDuration === opt.zoomIn && - selectedZoomOutDuration === opt.zoomOut; + selectedZoomInDuration !== undefined && + selectedZoomOutDuration !== undefined && + Math.round(selectedZoomInDuration) === Math.round(opt.zoomIn) && + Math.round(selectedZoomOutDuration) === Math.round(opt.zoomOut); return ( + ); + })} +
+ +
+
+ Blur intensity + + {Math.round(blurRegion.blurData?.intensity ?? DEFAULT_BLUR_DATA.intensity)}px + +
+ { + onBlurDataChange({ + ...DEFAULT_BLUR_DATA, + ...blurRegion.blurData, + intensity: values[0], + }); + }} + onValueCommit={() => onBlurDataCommit?.()} + min={MIN_BLUR_INTENSITY} + max={MAX_BLUR_INTENSITY} + step={1} + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + /> +
+ + + +
+
+ + {t("annotation.shortcutsAndTips")} +
+
    +
  • {t("annotation.tipMovePlayhead")}
  • +
+
+
+
+ ); +} diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index daf5f42..b1cd78d 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -42,11 +42,13 @@ import { cn } from "@/lib/utils"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; +import { BlurSettingsPanel } from "./BlurSettingsPanel"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; import type { AnnotationRegion, AnnotationType, + BlurData, CropRegion, FigureData, PlaybackSpeed, @@ -209,6 +211,11 @@ interface SettingsPanelProps { onAnnotationStyleChange?: (id: string, style: Partial) => void; onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void; onAnnotationDelete?: (id: string) => void; + selectedBlurId?: string | null; + blurRegions?: AnnotationRegion[]; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; + onBlurDelete?: (id: string) => void; selectedSpeedId?: string | null; selectedSpeedValue?: PlaybackSpeed | null; onSpeedChange?: (speed: PlaybackSpeed) => void; @@ -285,6 +292,11 @@ export function SettingsPanel({ onAnnotationStyleChange, onAnnotationFigureDataChange, onAnnotationDelete, + selectedBlurId, + blurRegions = [], + onBlurDataChange, + onBlurDataCommit, + onBlurDelete, selectedSpeedId, selectedSpeedValue, onSpeedChange, @@ -520,6 +532,9 @@ export function SettingsPanel({ const selectedAnnotation = selectedAnnotationId ? annotationRegions.find((a) => a.id === selectedAnnotationId) : null; + const selectedBlur = selectedBlurId + ? blurRegions.find((region) => region.id === selectedBlurId) + : null; // If an annotation is selected, show annotation settings instead if ( @@ -545,6 +560,17 @@ export function SettingsPanel({ ); } + if (selectedBlur && onBlurDataChange && onBlurDelete) { + return ( + onBlurDataChange(selectedBlur.id, blurData)} + onBlurDataCommit={onBlurDataCommit} + onDelete={() => onBlurDelete(selectedBlur.id)} + /> + ); + } + return (
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 88c3aae..a543da8 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -54,11 +54,13 @@ import { SettingsPanel } from "./SettingsPanel"; import TimelineEditor from "./timeline/TimelineEditor"; import { type AnnotationRegion, + type BlurData, type CursorTelemetryPoint, clampFocusToDepth, DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, + DEFAULT_BLUR_DATA, DEFAULT_FIGURE_DATA, DEFAULT_PLAYBACK_SPEED, DEFAULT_ZOOM_DEPTH, @@ -122,6 +124,7 @@ export default function VideoEditor() { const [selectedTrimId, setSelectedTrimId] = useState(null); const [selectedSpeedId, setSelectedSpeedId] = useState(null); const [selectedAnnotationId, setSelectedAnnotationId] = useState(null); + const [selectedBlurId, setSelectedBlurId] = useState(null); const [isExporting, setIsExporting] = useState(false); const [exportProgress, setExportProgress] = useState(null); const [exportError, setExportError] = useState(null); @@ -157,6 +160,15 @@ export default function VideoEditor() { const nextAnnotationZIndexRef = useRef(1); const exporterRef = useRef(null); + const annotationOnlyRegions = useMemo( + () => annotationRegions.filter((region) => region.type !== "blur"), + [annotationRegions], + ); + const blurRegions = useMemo( + () => annotationRegions.filter((region) => region.type === "blur"), + [annotationRegions], + ); + const currentProjectMedia = useMemo(() => { const screenVideoPath = videoSourcePath ?? (videoPath ? fromFileUrl(videoPath) : null); if (!screenVideoPath) { @@ -229,6 +241,7 @@ export default function VideoEditor() { setSelectedTrimId(null); setSelectedSpeedId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); nextZoomIdRef.current = deriveNextId( "zoom", @@ -626,7 +639,11 @@ export default function VideoEditor() { const handleSelectZoom = useCallback((id: string | null) => { setSelectedZoomId(id); - if (id) setSelectedTrimId(null); + if (id) { + setSelectedTrimId(null); + setSelectedAnnotationId(null); + setSelectedBlurId(null); + } }, []); const handleSelectTrim = useCallback((id: string | null) => { @@ -634,6 +651,7 @@ export default function VideoEditor() { if (id) { setSelectedZoomId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); } }, []); @@ -642,6 +660,16 @@ export default function VideoEditor() { if (id) { setSelectedZoomId(null); setSelectedTrimId(null); + setSelectedBlurId(null); + } + }, []); + + const handleSelectBlur = useCallback((id: string | null) => { + setSelectedBlurId(id); + if (id) { + setSelectedZoomId(null); + setSelectedTrimId(null); + setSelectedAnnotationId(null); } }, []); @@ -659,6 +687,7 @@ export default function VideoEditor() { setSelectedZoomId(id); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -677,6 +706,7 @@ export default function VideoEditor() { setSelectedZoomId(id); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -693,6 +723,7 @@ export default function VideoEditor() { setSelectedTrimId(id); setSelectedZoomId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -803,6 +834,7 @@ export default function VideoEditor() { setSelectedZoomId(null); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); } }, []); @@ -822,6 +854,7 @@ export default function VideoEditor() { setSelectedZoomId(null); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -888,6 +921,35 @@ export default function VideoEditor() { setSelectedAnnotationId(id); setSelectedZoomId(null); setSelectedTrimId(null); + setSelectedBlurId(null); + }, + [pushState], + ); + + const handleBlurAdded = useCallback( + (span: Span) => { + const id = `annotation-${nextAnnotationIdRef.current++}`; + const zIndex = nextAnnotationZIndexRef.current++; + const newRegion: AnnotationRegion = { + id, + startMs: Math.round(span.start), + endMs: Math.round(span.end), + type: "blur", + content: "", + position: { ...DEFAULT_ANNOTATION_POSITION }, + size: { ...DEFAULT_ANNOTATION_SIZE }, + style: { ...DEFAULT_ANNOTATION_STYLE }, + zIndex, + blurData: { ...DEFAULT_BLUR_DATA }, + }; + pushState((prev) => ({ + annotationRegions: [...prev.annotationRegions, newRegion], + })); + setSelectedBlurId(id); + setSelectedAnnotationId(null); + setSelectedZoomId(null); + setSelectedTrimId(null); + setSelectedSpeedId(null); }, [pushState], ); @@ -917,8 +979,11 @@ export default function VideoEditor() { if (selectedAnnotationId === id) { setSelectedAnnotationId(null); } + if (selectedBlurId === id) { + setSelectedBlurId(null); + } }, - [selectedAnnotationId, pushState], + [selectedAnnotationId, selectedBlurId, pushState], ); const handleAnnotationContentChange = useCallback( @@ -953,6 +1018,11 @@ export default function VideoEditor() { if (!region.figureData) { updatedRegion.figureData = { ...DEFAULT_FIGURE_DATA }; } + } else if (type === "blur") { + updatedRegion.content = ""; + if (!region.blurData) { + updatedRegion.blurData = { ...DEFAULT_BLUR_DATA }; + } } return updatedRegion; }), @@ -983,6 +1053,51 @@ export default function VideoEditor() { [pushState], ); + const handleBlurDataPreviewChange = useCallback( + (id: string, blurData: BlurData) => { + updateState((prev) => ({ + annotationRegions: prev.annotationRegions.map((region) => + region.id === id + ? { + ...region, + blurData, + // Freehand drawing area is the full video surface. + ...(blurData.shape === "freehand" + ? { + position: { x: 0, y: 0 }, + size: { width: 100, height: 100 }, + } + : {}), + } + : region, + ), + })); + }, + [updateState], + ); + + const handleBlurDataPanelChange = useCallback( + (id: string, blurData: BlurData) => { + pushState((prev) => ({ + annotationRegions: prev.annotationRegions.map((region) => + region.id === id + ? { + ...region, + blurData, + ...(blurData.shape === "freehand" + ? { + position: { x: 0, y: 0 }, + size: { width: 100, height: 100 }, + } + : {}), + } + : region, + ), + })); + }, + [pushState], + ); + const handleAnnotationPositionChange = useCallback( (id: string, position: { x: number; y: number }) => { pushState((prev) => ({ @@ -1100,7 +1215,10 @@ export default function VideoEditor() { ) { setSelectedAnnotationId(null); } - }, [selectedAnnotationId, annotationRegions]); + if (selectedBlurId && !annotationRegions.some((region) => region.id === selectedBlurId)) { + setSelectedBlurId(null); + } + }, [selectedAnnotationId, selectedBlurId, annotationRegions]); useEffect(() => { if (selectedSpeedId && !speedRegions.some((region) => region.id === selectedSpeedId)) { @@ -1675,11 +1793,18 @@ export default function VideoEditor() { cropRegion={cropRegion} trimRegions={trimRegions} speedRegions={speedRegions} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} selectedAnnotationId={selectedAnnotationId} onSelectAnnotation={handleSelectAnnotation} onAnnotationPositionChange={handleAnnotationPositionChange} onAnnotationSizeChange={handleAnnotationSizeChange} + blurRegions={blurRegions} + selectedBlurId={selectedBlurId} + onSelectBlur={handleSelectBlur} + onBlurPositionChange={handleAnnotationPositionChange} + onBlurSizeChange={handleAnnotationSizeChange} + onBlurDataChange={handleBlurDataPreviewChange} + onBlurDataCommit={commitState} cursorTelemetry={cursorTelemetry} />
@@ -1732,12 +1857,18 @@ export default function VideoEditor() { onSpeedDelete={handleSpeedDelete} selectedSpeedId={selectedSpeedId} onSelectSpeed={handleSelectSpeed} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} onAnnotationAdded={handleAnnotationAdded} onAnnotationSpanChange={handleAnnotationSpanChange} onAnnotationDelete={handleAnnotationDelete} selectedAnnotationId={selectedAnnotationId} onSelectAnnotation={handleSelectAnnotation} + blurRegions={blurRegions} + onBlurAdded={handleBlurAdded} + onBlurSpanChange={handleAnnotationSpanChange} + onBlurDelete={handleAnnotationDelete} + selectedBlurId={selectedBlurId} + onSelectBlur={handleSelectBlur} aspectRatio={aspectRatio} onAspectRatioChange={(ar) => pushState({ @@ -1830,12 +1961,17 @@ export default function VideoEditor() { )} onExport={handleOpenExportDialog} selectedAnnotationId={selectedAnnotationId} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} onAnnotationContentChange={handleAnnotationContentChange} onAnnotationTypeChange={handleAnnotationTypeChange} onAnnotationStyleChange={handleAnnotationStyleChange} onAnnotationFigureDataChange={handleAnnotationFigureDataChange} onAnnotationDelete={handleAnnotationDelete} + selectedBlurId={selectedBlurId} + blurRegions={blurRegions} + onBlurDataChange={handleBlurDataPanelChange} + onBlurDataCommit={commitState} + onBlurDelete={handleAnnotationDelete} selectedSpeedId={selectedSpeedId} selectedSpeedValue={ selectedSpeedId diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 08c1c25..caebe36 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -35,6 +35,7 @@ import { import { AnnotationOverlay } from "./AnnotationOverlay"; import { type AnnotationRegion, + type BlurData, type SpeedRegion, type TrimRegion, ZOOM_DEPTH_SCALES, @@ -101,6 +102,13 @@ interface VideoPlaybackProps { onSelectAnnotation?: (id: string | null) => void; onAnnotationPositionChange?: (id: string, position: { x: number; y: number }) => void; onAnnotationSizeChange?: (id: string, size: { width: number; height: number }) => void; + blurRegions?: AnnotationRegion[]; + selectedBlurId?: string | null; + onSelectBlur?: (id: string | null) => void; + onBlurPositionChange?: (id: string, position: { x: number; y: number }) => void; + onBlurSizeChange?: (id: string, size: { width: number; height: number }) => void; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; cursorTelemetry?: import("./types").CursorTelemetryPoint[]; } @@ -152,6 +160,13 @@ const VideoPlayback = forwardRef( onSelectAnnotation, onAnnotationPositionChange, onAnnotationSizeChange, + blurRegions = [], + selectedBlurId, + onSelectBlur, + onBlurPositionChange, + onBlurSizeChange, + onBlurDataChange, + onBlurDataCommit, cursorTelemetry = [], }, ref, @@ -166,6 +181,8 @@ const VideoPlayback = forwardRef( const timeUpdateAnimationRef = useRef(null); const [pixiReady, setPixiReady] = useState(false); const [videoReady, setVideoReady] = useState(false); + const [overlaySize, setOverlaySize] = useState({ width: 800, height: 600 }); + const [overlayElement, setOverlayElement] = useState(null); const overlayRef = useRef(null); const focusIndicatorRef = useRef(null); const [webcamLayout, setWebcamLayout] = useState(null); @@ -330,6 +347,11 @@ const VideoPlayback = forwardRef( layoutVideoContentRef.current = layoutVideoContent; }, [layoutVideoContent]); + const setOverlayRefs = useCallback((node: HTMLDivElement | null) => { + overlayRef.current = node; + setOverlayElement(node); + }, []); + const selectedZoom = useMemo(() => { if (!selectedZoomId) return null; return zoomRegions.find((region) => region.id === selectedZoomId) ?? null; @@ -623,7 +645,8 @@ const VideoPlayback = forwardRef( }, [selectedZoom, pixiReady, videoReady, updateOverlayForRegion]); useEffect(() => { - const overlayEl = overlayRef.current; + if (!pixiReady || !videoReady) return; + const overlayEl = overlayElement; if (!overlayEl) return; if (!selectedZoom) { overlayEl.style.cursor = "default"; @@ -632,7 +655,34 @@ const VideoPlayback = forwardRef( } overlayEl.style.cursor = isPlaying ? "not-allowed" : "grab"; overlayEl.style.pointerEvents = isPlaying ? "none" : "auto"; - }, [selectedZoom, isPlaying]); + }, [selectedZoom, isPlaying, pixiReady, videoReady, overlayElement]); + + useEffect(() => { + const overlayEl = overlayElement; + if (!overlayEl) return; + + const updateOverlaySize = () => { + const width = overlayEl.clientWidth || 800; + const height = overlayEl.clientHeight || 600; + setOverlaySize((prev) => { + if (prev.width === width && prev.height === height) return prev; + return { width, height }; + }); + }; + + updateOverlaySize(); + + if (typeof ResizeObserver !== "undefined") { + const observer = new ResizeObserver(() => { + updateOverlaySize(); + }); + observer.observe(overlayEl); + return () => observer.disconnect(); + } + + window.addEventListener("resize", updateOverlaySize); + return () => window.removeEventListener("resize", updateOverlaySize); + }, [overlayElement]); useEffect(() => { const container = containerRef.current; @@ -1287,7 +1337,7 @@ const VideoPlayback = forwardRef( {/* Only render overlay after PIXI and video are fully initialized */} {pixiReady && videoReady && (
( return sorted.map((annotation) => ( onAnnotationPositionChange?.(id, position)} onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)} onClick={handleAnnotationClick} @@ -1345,6 +1395,39 @@ const VideoPlayback = forwardRef( /> )); })()} + {(() => { + const filtered = (blurRegions || []).filter((blurRegion) => { + if (typeof blurRegion.startMs !== "number" || typeof blurRegion.endMs !== "number") + return false; + + if (blurRegion.id === selectedBlurId) return true; + + const timeMs = Math.round(currentTime * 1000); + return timeMs >= blurRegion.startMs && timeMs <= blurRegion.endMs; + }); + + const sorted = [...filtered].sort((a, b) => a.zIndex - b.zIndex); + const handleBlurClick = (clickedId: string) => { + onSelectBlur?.(clickedId); + }; + + return sorted.map((blurRegion) => ( + `${Math.round(p.x)}_${Math.round(p.y)}`).join("-")}`} + annotation={blurRegion} + isSelected={blurRegion.id === selectedBlurId} + containerWidth={overlaySize.width} + containerHeight={overlaySize.height} + onPositionChange={(id, position) => onBlurPositionChange?.(id, position)} + onSizeChange={(id, size) => onBlurSizeChange?.(id, size)} + onBlurDataChange={(id, blurData) => onBlurDataChange?.(id, blurData)} + onBlurDataCommit={onBlurDataCommit} + onClick={handleBlurClick} + zIndex={blurRegion.zIndex} + isSelectedBoost={blurRegion.id === selectedBlurId} + /> + )); + })()}
)}
+
+ )} {/* Device selectors — fixed above HUD bar, viewport-relative, never clipped */} {(showMicControls || showWebcamControls) && ( @@ -433,104 +484,133 @@ export function LaunchWindow() { {/* Record/Stop group */} {recording && ( - - - + + + + + + + + +
)} - {/* Restart recording */} - {recording && ( - - - + {!recording && ( + <> + {/* Open video file */} + + + + + {/* Open project */} + + + + )} - {/* Cancel recording */} - {recording && ( - + {/* Right sidebar controls */} +
+
- - )} - {/* Open video file */} - - - + {isLanguageMenuOpen && ( +
+ {SUPPORTED_LOCALES.map((loc) => ( + + ))} +
+ )} +
- {/* Open project */} - - - - - {/* Window controls */} -
- - + {/* Window controls */} +
+ + +
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 53e21e6..3326ee9 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -62,34 +62,50 @@ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayNam const SelectContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( - - - - & { + showScrollButtons?: boolean; + viewportClassName?: string; + } +>( + ( + { + className, + children, + position = "popper", + showScrollButtons = true, + viewportClassName, + ...props + }, + ref, + ) => ( + + - {children} - - - - -)); + {showScrollButtons ? : null} + + {children} + + {showScrollButtons ? : null} + + + ), +); SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index 0b75212..405d5c3 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -22,8 +22,13 @@ interface I18nContextValue { locale: Locale; setLocale: (locale: Locale) => void; t: (qualifiedKey: string, vars?: TranslateVars) => string; + systemLocaleSuggestion: Locale | null; + acceptSystemLocaleSuggestion: () => void; + dismissSystemLocaleSuggestion: () => void; } +const SYSTEM_LANGUAGE_PROMPT_SEEN_KEY = "openscreen-system-language-prompt-seen"; + const I18nContext = createContext(null); export function useI18n(): I18nContextValue { @@ -44,6 +49,35 @@ function isSupportedLocale(value: string): value is Locale { return (SUPPORTED_LOCALES as readonly string[]).includes(value); } +function getSupportedSystemLocale(): Locale | null { + if (typeof navigator === "undefined") return null; + + const candidates = + Array.isArray(navigator.languages) && navigator.languages.length > 0 + ? navigator.languages + : [navigator.language]; + + for (const candidate of candidates) { + if (!candidate) continue; + if (isSupportedLocale(candidate)) return candidate; + + const exactMatch = SUPPORTED_LOCALES.find( + (locale) => locale.toLowerCase() === candidate.toLowerCase(), + ); + if (exactMatch) return exactMatch; + + const baseLanguage = candidate.split("-")[0]?.toLowerCase(); + if (!baseLanguage) continue; + + if (baseLanguage === "zh") return "zh-CN"; + + const baseMatch = SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === baseLanguage); + if (baseMatch) return baseMatch; + } + + return null; +} + function getInitialLocale(): Locale { try { const stored = localStorage.getItem(LOCALE_STORAGE_KEY); @@ -56,6 +90,15 @@ function getInitialLocale(): Locale { export function I18nProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(getInitialLocale); + const [systemLocaleSuggestion, setSystemLocaleSuggestion] = useState(null); + + const markPromptAsHandled = useCallback(() => { + try { + localStorage.setItem(SYSTEM_LANGUAGE_PROMPT_SEEN_KEY, "1"); + } catch { + // localStorage may be unavailable + } + }, []); const setLocale = useCallback((newLocale: Locale) => { setLocaleState(newLocale); @@ -73,6 +116,46 @@ export function I18nProvider({ children }: { children: ReactNode }) { document.documentElement.lang = locale; }, [locale]); + useEffect(() => { + let hasStoredLocale = false; + let hasHandledSystemPrompt = false; + try { + const stored = localStorage.getItem(LOCALE_STORAGE_KEY); + hasStoredLocale = Boolean(stored && isSupportedLocale(stored)); + hasHandledSystemPrompt = localStorage.getItem(SYSTEM_LANGUAGE_PROMPT_SEEN_KEY) === "1"; + } catch { + // localStorage may be unavailable + } + + if (hasStoredLocale || hasHandledSystemPrompt || systemLocaleSuggestion) return; + + const detectedSystemLocale = getSupportedSystemLocale(); + if (!detectedSystemLocale || detectedSystemLocale === DEFAULT_LOCALE) { + markPromptAsHandled(); + return; + } + + setSystemLocaleSuggestion(detectedSystemLocale); + }, [markPromptAsHandled, systemLocaleSuggestion]); + + const acceptSystemLocaleSuggestion = useCallback(() => { + if (!systemLocaleSuggestion) return; + setLocale(systemLocaleSuggestion); + setSystemLocaleSuggestion(null); + markPromptAsHandled(); + }, [markPromptAsHandled, setLocale, systemLocaleSuggestion]); + + const dismissSystemLocaleSuggestion = useCallback(() => { + setSystemLocaleSuggestion(null); + try { + // Persisting default locale avoids showing this prompt again. + localStorage.setItem(LOCALE_STORAGE_KEY, DEFAULT_LOCALE); + } catch { + // localStorage may be unavailable + } + markPromptAsHandled(); + }, [markPromptAsHandled]); + const t = useCallback( (qualifiedKey: string, vars?: TranslateVars): string => { const dotIndex = qualifiedKey.indexOf("."); @@ -84,7 +167,24 @@ export function I18nProvider({ children }: { children: ReactNode }) { [locale], ); - const value = useMemo(() => ({ locale, setLocale, t }), [locale, setLocale, t]); + const value = useMemo( + () => ({ + locale, + setLocale, + t, + systemLocaleSuggestion, + acceptSystemLocaleSuggestion, + dismissSystemLocaleSuggestion, + }), + [ + locale, + setLocale, + t, + systemLocaleSuggestion, + acceptSystemLocaleSuggestion, + dismissSystemLocaleSuggestion, + ], + ); return {children}; } diff --git a/src/i18n/locales/en/launch.json b/src/i18n/locales/en/launch.json index cf111c4..e959a54 100644 --- a/src/i18n/locales/en/launch.json +++ b/src/i18n/locales/en/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "Please select a source to record" }, - "language": "Language" + "language": "Language", + "systemLanguagePrompt": { + "title": "Use your system language?", + "description": "We detected {{language}} as your system language. Do you want to switch OpenScreen to {{language}}?", + "switch": "Switch to {{language}}", + "keepDefault": "Keep current language" + } } diff --git a/src/i18n/locales/es/launch.json b/src/i18n/locales/es/launch.json index f47bc81..68919aa 100644 --- a/src/i18n/locales/es/launch.json +++ b/src/i18n/locales/es/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "Por favor selecciona una fuente para grabar" }, - "language": "Idioma" + "language": "Idioma", + "systemLanguagePrompt": { + "title": "¿Usar el idioma del sistema?", + "description": "Detectamos {{language}} como idioma de tu sistema. ¿Quieres cambiar OpenScreen a {{language}}?", + "switch": "Cambiar a {{language}}", + "keepDefault": "Mantener idioma actual" + } } diff --git a/src/i18n/locales/zh-CN/launch.json b/src/i18n/locales/zh-CN/launch.json index 6b63df1..a5c2a9d 100644 --- a/src/i18n/locales/zh-CN/launch.json +++ b/src/i18n/locales/zh-CN/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "请选择要录制的源" }, - "language": "语言" + "language": "语言", + "systemLanguagePrompt": { + "title": "使用系统语言吗?", + "description": "我们检测到你的系统语言是{{language}}。是否将 OpenScreen 切换为{{language}}?", + "switch": "切换到{{language}}", + "keepDefault": "保持当前语言" + } } From c9c2634db4b6bba20333ac96969f6c16a666706c Mon Sep 17 00:00:00 2001 From: imAaryash Date: Mon, 6 Apr 2026 10:11:07 +0530 Subject: [PATCH 050/152] fix(launch): polish language menu behavior --- src/components/launch/LaunchWindow.tsx | 103 ++++++++++--------------- src/contexts/I18nContext.tsx | 9 ++- 2 files changed, 46 insertions(+), 66 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 79a32d5..a430be0 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1,5 +1,5 @@ -import { ChevronDown, Languages } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { Check, ChevronDown, Languages } from "lucide-react"; +import { useEffect, useState } from "react"; import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs"; import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; @@ -28,6 +28,12 @@ import { requestCameraAccess } from "../../lib/requestCameraAccess"; import { formatTimePadded } from "../../utils/timeUtils"; import { AudioLevelMeter } from "../ui/audio-level-meter"; import { Button } from "../ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; import { Tooltip } from "../ui/tooltip"; import styles from "./LaunchWindow.module.css"; @@ -171,8 +177,6 @@ export function LaunchWindow() { const [selectedSource, setSelectedSource] = useState("Screen"); const [hasSelectedSource, setHasSelectedSource] = useState(false); - const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); - const languageMenuRef = useRef(null); useEffect(() => { const checkSelectedSource = async () => { @@ -194,31 +198,6 @@ export function LaunchWindow() { return () => clearInterval(interval); }, []); - useEffect(() => { - if (!isLanguageMenuOpen) return; - - const onPointerDown = (event: MouseEvent) => { - if (!languageMenuRef.current) return; - if (!languageMenuRef.current.contains(event.target as Node)) { - setIsLanguageMenuOpen(false); - } - }; - - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setIsLanguageMenuOpen(false); - } - }; - - document.addEventListener("mousedown", onPointerDown); - document.addEventListener("keydown", onKeyDown); - - return () => { - document.removeEventListener("mousedown", onPointerDown); - document.removeEventListener("keydown", onKeyDown); - }; - }, [isLanguageMenuOpen]); - const openSourceSelector = () => { if (window.electronAPI) { window.electronAPI.openSourceSelector(); @@ -557,42 +536,38 @@ export function LaunchWindow() { {/* Right sidebar controls */}
-
- - - {isLanguageMenuOpen && ( -
+ + - ))} -
- )} -
+
+ +
+ + + + + {SUPPORTED_LOCALES.map((loc) => ( + setLocale(loc)} + className={`flex items-center justify-between rounded-sm px-2 py-1.5 text-[11px] transition-colors ${loc === locale ? "text-white" : "text-white/90"} focus:bg-white/10 focus:text-white ${styles.electronNoDrag}`} + > + {getLocaleName(loc)} + {loc === locale ? : null} + + ))} + + {/* Window controls */}
diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index 405d5c3..f9c5ee5 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -5,6 +5,7 @@ import { useContext, useEffect, useMemo, + useRef, useState, } from "react"; import { @@ -91,6 +92,7 @@ function getInitialLocale(): Locale { export function I18nProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(getInitialLocale); const [systemLocaleSuggestion, setSystemLocaleSuggestion] = useState(null); + const hasRunSystemLocaleCheckRef = useRef(false); const markPromptAsHandled = useCallback(() => { try { @@ -117,6 +119,9 @@ export function I18nProvider({ children }: { children: ReactNode }) { }, [locale]); useEffect(() => { + if (hasRunSystemLocaleCheckRef.current) return; + hasRunSystemLocaleCheckRef.current = true; + let hasStoredLocale = false; let hasHandledSystemPrompt = false; try { @@ -127,7 +132,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { // localStorage may be unavailable } - if (hasStoredLocale || hasHandledSystemPrompt || systemLocaleSuggestion) return; + if (hasStoredLocale || hasHandledSystemPrompt) return; const detectedSystemLocale = getSupportedSystemLocale(); if (!detectedSystemLocale || detectedSystemLocale === DEFAULT_LOCALE) { @@ -136,7 +141,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { } setSystemLocaleSuggestion(detectedSystemLocale); - }, [markPromptAsHandled, systemLocaleSuggestion]); + }, [markPromptAsHandled]); const acceptSystemLocaleSuggestion = useCallback(() => { if (!systemLocaleSuggestion) return; From 97fbb01801ab85f6db072afc841de1bb830e2efa Mon Sep 17 00:00:00 2001 From: imAaryash Date: Mon, 6 Apr 2026 10:15:41 +0530 Subject: [PATCH 051/152] fix(i18n): resolve prompt persistence and language menu behavior --- src/components/launch/LaunchWindow.tsx | 8 ++++++-- src/contexts/I18nContext.tsx | 14 ++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index a430be0..137b28c 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -89,6 +89,7 @@ export function LaunchWindow() { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, } = useI18n(); const suggestedLanguageName = systemLocaleSuggestion ? getLocaleName(systemLocaleSuggestion) : ""; @@ -554,12 +555,15 @@ export function LaunchWindow() { side="top" sideOffset={6} collisionPadding={6} - className={`w-36 min-w-0 max-h-none overflow-hidden border-white/15 bg-[rgba(24,24,34,0.98)] p-1 text-white shadow-2xl backdrop-blur-xl ${styles.electronNoDrag}`} + className={`w-36 min-w-0 max-h-none overflow-y-hidden overflow-x-hidden border-white/15 bg-[rgba(24,24,34,0.98)] p-1 text-white shadow-2xl backdrop-blur-xl ${styles.electronNoDrag}`} > {SUPPORTED_LOCALES.map((loc) => ( setLocale(loc)} + onSelect={() => { + setLocale(loc); + resolveSystemLocaleSuggestion(); + }} className={`flex items-center justify-between rounded-sm px-2 py-1.5 text-[11px] transition-colors ${loc === locale ? "text-white" : "text-white/90"} focus:bg-white/10 focus:text-white ${styles.electronNoDrag}`} > {getLocaleName(loc)} diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index f9c5ee5..84640ea 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -26,6 +26,7 @@ interface I18nContextValue { systemLocaleSuggestion: Locale | null; acceptSystemLocaleSuggestion: () => void; dismissSystemLocaleSuggestion: () => void; + resolveSystemLocaleSuggestion: () => void; } const SYSTEM_LANGUAGE_PROMPT_SEEN_KEY = "openscreen-system-language-prompt-seen"; @@ -152,12 +153,11 @@ export function I18nProvider({ children }: { children: ReactNode }) { const dismissSystemLocaleSuggestion = useCallback(() => { setSystemLocaleSuggestion(null); - try { - // Persisting default locale avoids showing this prompt again. - localStorage.setItem(LOCALE_STORAGE_KEY, DEFAULT_LOCALE); - } catch { - // localStorage may be unavailable - } + markPromptAsHandled(); + }, [markPromptAsHandled]); + + const resolveSystemLocaleSuggestion = useCallback(() => { + setSystemLocaleSuggestion(null); markPromptAsHandled(); }, [markPromptAsHandled]); @@ -180,6 +180,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, }), [ locale, @@ -188,6 +189,7 @@ export function I18nProvider({ children }: { children: ReactNode }) { systemLocaleSuggestion, acceptSystemLocaleSuggestion, dismissSystemLocaleSuggestion, + resolveSystemLocaleSuggestion, ], ); From e96478e8130b6d0bb59ce3dcd67f25791f62e7a9 Mon Sep 17 00:00:00 2001 From: imAaryash Date: Sun, 12 Apr 2026 04:22:08 +0530 Subject: [PATCH 052/152] Revert "Merge pull request #365 from AmitwalaH/fix-tutorial-translations" This reverts commit 5494acb5bafa303bdc54d521d7cf913d07edfb08. --- src/i18n/locales/en/dialogs.json | 13 +++++-------- src/i18n/locales/es/dialogs.json | 12 +++++------- src/i18n/locales/zh-CN/dialogs.json | 12 +++++------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/i18n/locales/en/dialogs.json b/src/i18n/locales/en/dialogs.json index a84b5fd..66a33c2 100644 --- a/src/i18n/locales/en/dialogs.json +++ b/src/i18n/locales/en/dialogs.json @@ -27,11 +27,10 @@ "triggerLabel": "How trimming works", "title": "How Trimming Works", "description": "Understanding how to cut out unwanted parts of your video.", - "explanationBefore": "The Trim tool works by defining the segments you want to", - "remove": "remove", - "explanationMiddle": " — anything", - "covered": "covered", - "explanationAfter": "by a red trim segment will be cut out when you export.", + "explanation": "The Trim tool works by defining the segments you want to", + "explanationRemove": "remove", + "explanationCovered": "covered", + "explanationEnd": "by a red trim segment will be cut out when you export.", "visualExample": "Visual Example", "removed": "REMOVED", "kept": "Kept", @@ -40,9 +39,7 @@ "part3": "Part 3", "finalVideo": "Final Video", "step1Title": "1. Add Trim", - "step1DescriptionBefore": "Press ", - "step1DescriptionAfter": " or click the scissors icon to mark a section for removal.", - + "step1Description": "Press T or click the scissors icon to mark a section for removal.", "step2Title": "2. Adjust", "step2Description": "Drag the edges of the red region to cover exactly what you want to cut out." }, diff --git a/src/i18n/locales/es/dialogs.json b/src/i18n/locales/es/dialogs.json index f8a5e63..acf2a04 100644 --- a/src/i18n/locales/es/dialogs.json +++ b/src/i18n/locales/es/dialogs.json @@ -27,11 +27,10 @@ "triggerLabel": "Cómo funciona el recorte", "title": "Cómo funciona el recorte", "description": "Aprende a eliminar las partes no deseadas de tu video.", - "explanationBefore": "La herramienta de recorte funciona definiendo los segmentos que deseas", - "remove": "eliminar", - "explanationMiddle": " — cualquier parte", - "covered": "cubierta", - "explanationAfter": "por un segmento rojo será eliminada al exportar.", + "explanation": "La herramienta de recorte funciona definiendo los segmentos que deseas", + "explanationRemove": "eliminar", + "explanationCovered": "cubierto", + "explanationEnd": "por un segmento rojo de recorte será eliminado al exportar.", "visualExample": "Ejemplo visual", "removed": "ELIMINADO", "kept": "Conservado", @@ -40,8 +39,7 @@ "part3": "Parte 3", "finalVideo": "Video final", "step1Title": "1. Agregar recorte", - "step1DescriptionBefore": "Presiona ", - "step1DescriptionAfter": " o haz clic en el ícono de tijeras para marcar una sección a eliminar.", + "step1Description": "Presiona T o haz clic en el ícono de tijeras para marcar una sección a eliminar.", "step2Title": "2. Ajustar", "step2Description": "Arrastra los bordes de la región roja para cubrir exactamente lo que deseas eliminar." }, diff --git a/src/i18n/locales/zh-CN/dialogs.json b/src/i18n/locales/zh-CN/dialogs.json index 0385b36..3f181bc 100644 --- a/src/i18n/locales/zh-CN/dialogs.json +++ b/src/i18n/locales/zh-CN/dialogs.json @@ -27,11 +27,10 @@ "triggerLabel": "剪辑功能说明", "title": "剪辑功能说明", "description": "了解如何剪掉视频中不需要的部分。", - "explanationBefore": "剪辑工具通过定义您要", - "remove": "移除", - "explanationMiddle": "——任何被", - "covered": "覆盖", - "explanationAfter": "的红色剪辑区域部分将在导出时被剪掉。", + "explanation": "剪辑工具通过定义您要", + "explanationRemove": "移除", + "explanationCovered": "覆盖", + "explanationEnd": "的片段来工作。被红色剪辑区域覆盖的部分将在导出时被剪掉。", "visualExample": "示例演示", "removed": "已移除", "kept": "保留", @@ -40,8 +39,7 @@ "part3": "第 3 部分", "finalVideo": "最终视频", "step1Title": "1. 添加剪辑", - "step1DescriptionBefore": "按", - "step1DescriptionAfter": "键或点击剪刀图标来标记要移除的片段。", + "step1Description": "按 T 或点击剪刀图标来标记要移除的片段。", "step2Title": "2. 调整", "step2Description": "拖动红色区域的边缘,精确覆盖您要剪掉的部分。" }, From d1c9555464ecc59a70972c95bf60462386847cde Mon Sep 17 00:00:00 2001 From: imAaryash Date: Sun, 12 Apr 2026 05:13:31 +0530 Subject: [PATCH 053/152] feat(i18n): auto-discover valid locales and harden language menu - derive available locales from locale folders with required namespace validation - exclude incomplete locales and report missing namespace files - align system-language suggestion and selectors with discovered locales - improve launch HUD language menu interaction, scrolling, and viewport clipping - make i18n-check discover locale folders automatically --- scripts/i18n-check.mjs | 14 +- src/components/launch/LaunchWindow.module.css | 75 ++++++++ src/components/launch/LaunchWindow.tsx | 175 +++++++++++++----- src/components/ui/dropdown-menu.tsx | 18 +- src/components/video-editor/VideoEditor.tsx | 7 +- src/contexts/I18nContext.tsx | 19 +- src/i18n/config.ts | 3 +- src/i18n/loader.ts | 77 +++++++- 8 files changed, 314 insertions(+), 74 deletions(-) diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index ca73b23..699ae9e 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** * Validates that all locale translation files have identical key structures. - * Compares zh-CN and es against the en baseline for every namespace. + * Compares all locale folders (except en) against the en baseline for every namespace. * * Usage: node scripts/i18n-check.mjs */ @@ -11,7 +11,6 @@ import path from "node:path"; const LOCALES_DIR = path.resolve("src/i18n/locales"); const BASE_LOCALE = "en"; -const COMPARE_LOCALES = ["zh-CN", "es", "tr", "ko-KR"]; function getKeys(obj, prefix = "") { const keys = []; @@ -34,12 +33,19 @@ const namespaces = fs .filter((f) => f.endsWith(".json")) .map((f) => f.replace(".json", "")); +const compareLocales = fs + .readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((locale) => locale !== BASE_LOCALE) + .sort((a, b) => a.localeCompare(b)); + for (const namespace of namespaces) { const basePath = path.join(baseDir, `${namespace}.json`); const baseData = JSON.parse(fs.readFileSync(basePath, "utf-8")); const baseKeys = getKeys(baseData); - for (const locale of COMPARE_LOCALES) { + for (const locale of compareLocales) { const localePath = path.join(LOCALES_DIR, locale, `${namespace}.json`); if (!fs.existsSync(localePath)) { @@ -77,6 +83,6 @@ if (hasErrors) { process.exit(1); } else { console.log( - `i18n check PASSED — all ${COMPARE_LOCALES.length} locales match ${BASE_LOCALE} across ${namespaces.length} namespaces.`, + `i18n check PASSED — all ${compareLocales.length} locales match ${BASE_LOCALE} across ${namespaces.length} namespaces.`, ); } diff --git a/src/components/launch/LaunchWindow.module.css b/src/components/launch/LaunchWindow.module.css index ff68c3d..132fa0a 100644 --- a/src/components/launch/LaunchWindow.module.css +++ b/src/components/launch/LaunchWindow.module.css @@ -6,3 +6,78 @@ .electronNoDrag { -webkit-app-region: no-drag; } + +.languageMenuScroll { + max-height: 16rem; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; + touch-action: pan-y; + -webkit-overflow-scrolling: touch; +} + +.languageMenuScroll::-webkit-scrollbar { + width: 8px; +} + +.languageMenuScroll::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.04); + border-radius: 999px; +} + +.languageMenuScroll::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0.2)); + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.languageMenuScroll::-webkit-scrollbar-thumb:hover { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.3)); +} + +.languageMenuContainer { + position: relative; + z-index: 20; +} + +.languageMenuPanel { + position: fixed; + right: 0; + top: 0; + width: 12rem; + padding: 0.375rem; + border-radius: 0.75rem; + border: 1px solid rgba(255, 255, 255, 0.14); + background: linear-gradient(160deg, rgba(28, 29, 42, 0.98), rgba(18, 19, 28, 0.98)); + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.55); + backdrop-filter: blur(14px); + pointer-events: auto; + box-sizing: border-box; +} + +.languageMenuItem { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.625rem; + border-radius: 0.5rem; + font-size: 11px; + color: rgba(255, 255, 255, 0.88); + background: transparent; + border: 0; + cursor: pointer; + transition: background-color 120ms ease, color 120ms ease; +} + +.languageMenuItem:hover, +.languageMenuItem:focus-visible { + background: rgba(255, 255, 255, 0.1); + color: #ffffff; + outline: none; +} + +.languageMenuItemActive { + background: rgba(255, 255, 255, 0.12); + color: #ffffff; +} diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 137b28c..2914584 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1,5 +1,6 @@ import { Check, ChevronDown, Languages } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs"; import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; @@ -18,8 +19,7 @@ import { } from "react-icons/md"; import { RxDragHandleDots2 } from "react-icons/rx"; import { useI18n, useScopedT } from "@/contexts/I18nContext"; -import { SUPPORTED_LOCALES } from "@/i18n/config"; -import { getLocaleName } from "@/i18n/loader"; +import { getAvailableLocales, getLocaleName } from "@/i18n/loader"; import { useAudioLevelMeter } from "../../hooks/useAudioLevelMeter"; import { useCameraDevices } from "../../hooks/useCameraDevices"; import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices"; @@ -28,12 +28,6 @@ import { requestCameraAccess } from "../../lib/requestCameraAccess"; import { formatTimePadded } from "../../utils/timeUtils"; import { AudioLevelMeter } from "../ui/audio-level-meter"; import { Button } from "../ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; import { Tooltip } from "../ui/tooltip"; import styles from "./LaunchWindow.module.css"; @@ -83,6 +77,7 @@ const hudSidebarClasses = "ml-0.5 pl-1.5 border-l border-white/10 flex items-cen export function LaunchWindow() { const t = useScopedT("launch"); + const availableLocales = getAvailableLocales(); const { locale, setLocale, @@ -123,6 +118,18 @@ export function LaunchWindow() { const [isWebcamHovered, setIsWebcamHovered] = useState(false); const [isWebcamFocused, setIsWebcamFocused] = useState(false); const webcamExpanded = isWebcamHovered || isWebcamFocused; + const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); + const languageTriggerRef = useRef(null); + const languageMenuPanelRef = useRef(null); + const [languageMenuStyle, setLanguageMenuStyle] = useState<{ + right: number; + top: number; + maxHeight: number; + }>({ + right: 12, + top: 12, + maxHeight: 240, + }); const { devices: micDevices, @@ -176,6 +183,71 @@ export function LaunchWindow() { }); }, []); + useEffect(() => { + if (!isLanguageMenuOpen) return; + + const handlePointerDown = (event: PointerEvent) => { + const target = event.target as Node; + const clickedTrigger = languageTriggerRef.current?.contains(target); + const clickedMenu = languageMenuPanelRef.current?.contains(target); + if (!clickedTrigger && !clickedMenu) { + setIsLanguageMenuOpen(false); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setIsLanguageMenuOpen(false); + } + }; + + window.addEventListener("pointerdown", handlePointerDown); + window.addEventListener("keydown", handleEscape); + + return () => { + window.removeEventListener("pointerdown", handlePointerDown); + window.removeEventListener("keydown", handleEscape); + }; + }, [isLanguageMenuOpen]); + + useEffect(() => { + if (!isLanguageMenuOpen || !languageTriggerRef.current) return; + + const updatePosition = () => { + if (!languageTriggerRef.current) return; + const rect = languageTriggerRef.current.getBoundingClientRect(); + const gap = 8; + const viewportPadding = 8; + const availableHeight = Math.max(80, rect.top - viewportPadding - gap); + const top = Math.max(viewportPadding, rect.top - gap - availableHeight); + + setLanguageMenuStyle({ + right: Math.max(viewportPadding, window.innerWidth - rect.right), + top, + maxHeight: availableHeight, + }); + }; + + updatePosition(); + window.addEventListener("resize", updatePosition); + window.addEventListener("scroll", updatePosition, true); + + return () => { + window.removeEventListener("resize", updatePosition); + window.removeEventListener("scroll", updatePosition, true); + }; + }, [isLanguageMenuOpen]); + + useEffect(() => { + if (!isLanguageMenuOpen || !languageMenuPanelRef.current) return; + const id = requestAnimationFrame(() => { + if (languageMenuPanelRef.current) { + languageMenuPanelRef.current.scrollTop = 0; + } + }); + return () => cancelAnimationFrame(id); + }, [isLanguageMenuOpen]); + const [selectedSource, setSelectedSource] = useState("Screen"); const [hasSelectedSource, setHasSelectedSource] = useState(false); @@ -537,41 +609,60 @@ export function LaunchWindow() { {/* Right sidebar controls */}
- - - - - - + +
+ + {isLanguageMenuOpen + ? createPortal( +
event.stopPropagation()} > - {getLocaleName(loc)} - {loc === locale ? : null} - - ))} - - + {availableLocales.map((loc) => ( + + ))} +
, + document.body, + ) + : null} {/* Window controls */}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index c15187d..f4dd29f 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -54,9 +54,11 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - + React.ComponentPropsWithoutRef & { + portalled?: boolean; + } +>(({ className, sideOffset = 4, portalled = true, ...props }, ref) => { + const content = ( - -)); + ); + + if (!portalled) { + return content; + } + + return {content}; +}); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 0321f43..47c5668 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -14,8 +14,8 @@ import { import { useI18n, useScopedT } from "@/contexts/I18nContext"; import { useShortcuts } from "@/contexts/ShortcutsContext"; import { INITIAL_EDITOR_STATE, useEditorHistory } from "@/hooks/useEditorHistory"; -import { type Locale, SUPPORTED_LOCALES } from "@/i18n/config"; -import { getLocaleName } from "@/i18n/loader"; +import { type Locale } from "@/i18n/config"; +import { getAvailableLocales, getLocaleName } from "@/i18n/loader"; import { calculateOutputDimensions, type ExportFormat, @@ -154,6 +154,7 @@ export default function VideoEditor() { const { shortcuts, isMac } = useShortcuts(); const t = useScopedT("editor"); const ts = useScopedT("settings"); + const availableLocales = getAvailableLocales(); const { locale, setLocale } = useI18n(); const nextAnnotationIdRef = useRef(1); @@ -1707,7 +1708,7 @@ export default function VideoEditor() { className="bg-transparent text-[11px] font-medium outline-none cursor-pointer appearance-none pr-1" style={{ color: "inherit" }} > - {SUPPORTED_LOCALES.map((loc) => ( + {availableLocales.map((loc) => ( diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx index 84640ea..1056749 100644 --- a/src/contexts/I18nContext.tsx +++ b/src/contexts/I18nContext.tsx @@ -8,14 +8,8 @@ import { useRef, useState, } from "react"; -import { - DEFAULT_LOCALE, - type I18nNamespace, - LOCALE_STORAGE_KEY, - type Locale, - SUPPORTED_LOCALES, -} from "@/i18n/config"; -import { translate } from "@/i18n/loader"; +import { DEFAULT_LOCALE, type I18nNamespace, LOCALE_STORAGE_KEY, type Locale } from "@/i18n/config"; +import { getAvailableLocales, translate } from "@/i18n/loader"; type TranslateVars = Record; @@ -48,11 +42,12 @@ export function useScopedT(namespace: I18nNamespace) { } function isSupportedLocale(value: string): value is Locale { - return (SUPPORTED_LOCALES as readonly string[]).includes(value); + return getAvailableLocales().includes(value); } function getSupportedSystemLocale(): Locale | null { if (typeof navigator === "undefined") return null; + const availableLocales = getAvailableLocales(); const candidates = Array.isArray(navigator.languages) && navigator.languages.length > 0 @@ -63,7 +58,7 @@ function getSupportedSystemLocale(): Locale | null { if (!candidate) continue; if (isSupportedLocale(candidate)) return candidate; - const exactMatch = SUPPORTED_LOCALES.find( + const exactMatch = availableLocales.find( (locale) => locale.toLowerCase() === candidate.toLowerCase(), ); if (exactMatch) return exactMatch; @@ -71,9 +66,9 @@ function getSupportedSystemLocale(): Locale | null { const baseLanguage = candidate.split("-")[0]?.toLowerCase(); if (!baseLanguage) continue; - if (baseLanguage === "zh") return "zh-CN"; + if (baseLanguage === "zh" && availableLocales.includes("zh-CN")) return "zh-CN"; - const baseMatch = SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === baseLanguage); + const baseMatch = availableLocales.find((locale) => locale.toLowerCase() === baseLanguage); if (baseMatch) return baseMatch; } diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 0933569..507aa4d 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,5 +1,4 @@ export const DEFAULT_LOCALE = "en" as const; -export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", "fr", "tr", "ko-KR"] as const; export const I18N_NAMESPACES = [ "common", "dialogs", @@ -10,7 +9,7 @@ export const I18N_NAMESPACES = [ "timeline", ] as const; -export type Locale = (typeof SUPPORTED_LOCALES)[number]; +export type Locale = string; export type I18nNamespace = (typeof I18N_NAMESPACES)[number]; export const LOCALE_STORAGE_KEY = "openscreen-locale"; diff --git a/src/i18n/loader.ts b/src/i18n/loader.ts index 4736db8..36d8eb6 100644 --- a/src/i18n/loader.ts +++ b/src/i18n/loader.ts @@ -1,6 +1,10 @@ -import { DEFAULT_LOCALE, type I18nNamespace, type Locale } from "./config"; +import { DEFAULT_LOCALE, I18N_NAMESPACES, type I18nNamespace, type Locale } from "./config"; type MessageMap = Record; +type LocaleValidationError = { + locale: string; + missingNamespaces: I18nNamespace[]; +}; const modules = import.meta.glob("./locales/**/*.json", { eager: true }) as Record< string, @@ -18,6 +22,62 @@ for (const [path, mod] of Object.entries(modules)) { messages[locale][namespace] = mod.default; } +const REQUIRED_NAMESPACES = new Set(I18N_NAMESPACES); + +const localeValidationErrors: LocaleValidationError[] = Object.keys(messages) + .map((locale) => { + const localeMessages = messages[locale] ?? {}; + const missingNamespaces = I18N_NAMESPACES.filter((namespace) => !localeMessages[namespace]); + return { + locale, + missingNamespaces, + }; + }) + .filter((entry) => entry.missingNamespaces.length > 0); + +const invalidLocales = new Set(localeValidationErrors.map((entry) => entry.locale)); + +const availableLocales = Object.keys(messages) + .filter((locale) => REQUIRED_NAMESPACES.size > 0 && hasRequiredNamespaces(messages[locale])) + .filter((locale) => !invalidLocales.has(locale)) + .sort((a, b) => { + if (a === DEFAULT_LOCALE) return -1; + if (b === DEFAULT_LOCALE) return 1; + return a.localeCompare(b); + }); + +if (localeValidationErrors.length > 0) { + console.error("[i18n] Incomplete locale folders were excluded:"); + for (const entry of localeValidationErrors) { + console.error( + `[i18n] ${entry.locale}: missing ${entry.missingNamespaces.map((ns) => `${ns}.json`).join(", ")}`, + ); + } +} + +function hasRequiredNamespaces(localeMessages: Record | undefined): boolean { + if (!localeMessages) return false; + for (const namespace of REQUIRED_NAMESPACES) { + if (!localeMessages[namespace]) return false; + } + return true; +} + +function isAvailableLocale(locale: string): locale is Locale { + return availableLocales.includes(locale); +} + +export function getAvailableLocales(): Locale[] { + if (availableLocales.length === 0) { + return [DEFAULT_LOCALE]; + } + return availableLocales; +} + +export function getLocaleValidationErrors(): LocaleValidationError[] { + return localeValidationErrors; +} + function getMessageValue(obj: unknown, dotPath: string): string | undefined { const keys = dotPath.split("."); let current: unknown = obj; @@ -34,15 +94,18 @@ function interpolate(str: string, vars?: Record): strin } export function getMessages(locale: Locale, namespace: I18nNamespace): MessageMap { - return messages[locale]?.[namespace] ?? {}; + const resolvedLocale = isAvailableLocale(locale) ? locale : DEFAULT_LOCALE; + return messages[resolvedLocale]?.[namespace] ?? {}; } export function getLocaleName(locale: Locale): string { - return getMessageValue(messages[locale]?.common, "locale.name") ?? locale; + const resolvedLocale = isAvailableLocale(locale) ? locale : DEFAULT_LOCALE; + return getMessageValue(messages[resolvedLocale]?.common, "locale.name") ?? locale; } export function getLocaleShort(locale: Locale): string { - return getMessageValue(messages[locale]?.common, "locale.short") ?? locale; + const resolvedLocale = isAvailableLocale(locale) ? locale : DEFAULT_LOCALE; + return getMessageValue(messages[resolvedLocale]?.common, "locale.short") ?? locale; } export function translate( @@ -52,8 +115,10 @@ export function translate( vars?: Record, ): string { const value = - getMessageValue(messages[locale]?.[namespace], key) ?? - getMessageValue(messages[DEFAULT_LOCALE]?.[namespace], key); + getMessageValue( + messages[isAvailableLocale(locale) ? locale : DEFAULT_LOCALE]?.[namespace], + key, + ) ?? getMessageValue(messages[DEFAULT_LOCALE]?.[namespace], key); if (value == null) return `${namespace}.${key}`; return interpolate(value, vars); From 0efd2d64ed0dd2bad26e50c500888cfec2fc13b3 Mon Sep 17 00:00:00 2001 From: SimulAffect <248947347+SimulAffect@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:26:45 +0800 Subject: [PATCH 054/152] fix(i18n): sync tutorial help translations --- .../tutorialHelpTranslations.test.ts | 59 +++++++++++++++++++ src/i18n/locales/fr/dialogs.json | 12 ++-- src/i18n/locales/tr/dialogs.json | 12 ++-- 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/i18n/__tests__/tutorialHelpTranslations.test.ts diff --git a/src/i18n/__tests__/tutorialHelpTranslations.test.ts b/src/i18n/__tests__/tutorialHelpTranslations.test.ts new file mode 100644 index 0000000..fcfa9d3 --- /dev/null +++ b/src/i18n/__tests__/tutorialHelpTranslations.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from "vitest"; +import { type Locale, SUPPORTED_LOCALES } from "@/i18n/config"; +import enDialogs from "@/i18n/locales/en/dialogs.json"; +import esDialogs from "@/i18n/locales/es/dialogs.json"; +import frDialogs from "@/i18n/locales/fr/dialogs.json"; +import koKRDialogs from "@/i18n/locales/ko-KR/dialogs.json"; +import trDialogs from "@/i18n/locales/tr/dialogs.json"; +import zhCNDialogs from "@/i18n/locales/zh-CN/dialogs.json"; + +const tutorialHelpKeys = [ + "triggerLabel", + "title", + "description", + "explanationBefore", + "remove", + "explanationMiddle", + "covered", + "explanationAfter", + "visualExample", + "removed", + "kept", + "part1", + "part2", + "part3", + "finalVideo", + "step1Title", + "step1DescriptionBefore", + "step1DescriptionAfter", + "step2Title", + "step2Description", +] as const; + +const keysThatMayBeEmpty = new Set<(typeof tutorialHelpKeys)[number]>(["step1DescriptionBefore"]); + +const dialogsByLocale = { + en: enDialogs, + "zh-CN": zhCNDialogs, + es: esDialogs, + fr: frDialogs, + tr: trDialogs, + "ko-KR": koKRDialogs, +} satisfies Record }>; + +describe("TutorialHelp translations", () => { + it("defines every tutorial help key for each supported locale", () => { + for (const locale of SUPPORTED_LOCALES) { + const tutorial = dialogsByLocale[locale].tutorial; + + for (const key of tutorialHelpKeys) { + const message = tutorial[key]; + const label = `${locale} dialogs.tutorial.${key}`; + expect(message, label).toEqual(expect.any(String)); + if (!keysThatMayBeEmpty.has(key)) { + expect((message as string).trim().length, label).toBeGreaterThan(0); + } + } + } + }); +}); diff --git a/src/i18n/locales/fr/dialogs.json b/src/i18n/locales/fr/dialogs.json index b4056a5..fc32e6b 100644 --- a/src/i18n/locales/fr/dialogs.json +++ b/src/i18n/locales/fr/dialogs.json @@ -27,10 +27,11 @@ "triggerLabel": "Comment fonctionne la coupe", "title": "Comment fonctionne la coupe", "description": "Comprendre comment supprimer les parties indésirables de votre vidéo.", - "explanation": "L'outil Coupe fonctionne en définissant les segments que vous souhaitez", - "explanationRemove": "supprimer", - "explanationCovered": "couvert", - "explanationEnd": "par un segment de coupe rouge sera coupé lors de l'export.", + "explanationBefore": "L'outil Coupe fonctionne en définissant les segments que vous souhaitez", + "remove": "supprimer", + "explanationMiddle": " — tout élément", + "covered": "couvert", + "explanationAfter": "par un segment de coupe rouge sera coupé lors de l'export.", "visualExample": "Exemple visuel", "removed": "SUPPRIMÉ", "kept": "Conservé", @@ -39,7 +40,8 @@ "part3": "Partie 3", "finalVideo": "Vidéo finale", "step1Title": "1. Ajouter une coupe", - "step1Description": "Appuyez sur T ou cliquez sur l'icône ciseaux pour marquer une section à supprimer.", + "step1DescriptionBefore": "Appuyez sur ", + "step1DescriptionAfter": " ou cliquez sur l'icône ciseaux pour marquer une section à supprimer.", "step2Title": "2. Ajuster", "step2Description": "Faites glisser les bords de la région rouge pour couvrir exactement ce que vous souhaitez couper." }, diff --git a/src/i18n/locales/tr/dialogs.json b/src/i18n/locales/tr/dialogs.json index 5661e45..9fab50d 100644 --- a/src/i18n/locales/tr/dialogs.json +++ b/src/i18n/locales/tr/dialogs.json @@ -27,10 +27,11 @@ "triggerLabel": "Kırpma nasıl çalışır", "title": "Kırpma Nasıl Çalışır", "description": "Videonuzun istenmeyen bölümlerini nasıl keseceğinizi anlayın.", - "explanation": "Kırpma aracı, kaldırmak istediğiniz bölümleri tanımlayarak çalışır.", - "explanationRemove": "kaldırmak", - "explanationCovered": "kaplanan", - "explanationEnd": "kırmızı kırpma bölgesi ile işaretlenen kısımlar dışa aktarımda kesilecektir.", + "explanationBefore": "Kırpma aracı, istediğiniz bölümleri", + "remove": "kaldırmak", + "explanationMiddle": " için kullanılır; kırmızı kırpma bölgesiyle", + "covered": "kaplanan", + "explanationAfter": "her şey dışa aktarımda kesilecektir.", "visualExample": "Görsel Örnek", "removed": "KALDIRILDI", "kept": "Korundu", @@ -39,7 +40,8 @@ "part3": "Bölüm 3", "finalVideo": "Son Video", "step1Title": "1. Kırpma Ekle", - "step1Description": "Kaldırılacak bölümü işaretlemek için T tuşuna basın veya makas simgesine tıklayın.", + "step1DescriptionBefore": "Kaldırılacak bölümü işaretlemek için ", + "step1DescriptionAfter": " tuşuna basın veya makas simgesine tıklayın.", "step2Title": "2. Ayarla", "step2Description": "Kesmek istediğiniz kısmı tam olarak kaplamak için kırmızı bölgenin kenarlarını sürükleyin." }, From 8bcce473d5bcef718d8baa3b8c801c9f5df2b87c Mon Sep 17 00:00:00 2001 From: LorenzoLancia Date: Sun, 12 Apr 2026 18:04:43 +0200 Subject: [PATCH 055/152] feat: add mosaic blur with black shading --- .../video-editor/AnnotationOverlay.tsx | 195 +++++++++++++++++- .../video-editor/BlurSettingsPanel.tsx | 117 ++++++++++- src/components/video-editor/VideoPlayback.tsx | 17 +- .../video-editor/projectPersistence.test.ts | 69 +++++++ .../video-editor/projectPersistence.ts | 11 + src/components/video-editor/types.ts | 11 + src/i18n/locales/en/settings.json | 7 + src/i18n/locales/es/settings.json | 7 + src/i18n/locales/fr/settings.json | 7 + src/lib/blurEffects.test.ts | 80 +++++++ src/lib/blurEffects.ts | 113 ++++++++++ src/lib/exporter/annotationRenderer.ts | 45 ++-- 12 files changed, 644 insertions(+), 35 deletions(-) create mode 100644 src/lib/blurEffects.test.ts create mode 100644 src/lib/blurEffects.ts diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 3120f0b..f416c32 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -1,15 +1,27 @@ -import { type CSSProperties, type PointerEvent, useRef, useState } from "react"; +import { type CSSProperties, type PointerEvent, useEffect, useRef, useState } from "react"; import { Rnd } from "react-rnd"; +import { + getBlurOverlayColor, + getMosaicGridOverlayColor, + getNormalizedMosaicBlockSize, +} from "@/lib/blurEffects"; import { cn } from "@/lib/utils"; import { getArrowComponent } from "./ArrowSvgs"; import { type AnnotationRegion, type BlurData, + DEFAULT_BLUR_BLOCK_SIZE, DEFAULT_BLUR_DATA, DEFAULT_BLUR_INTENSITY, } from "./types"; const FREEHAND_POINT_THRESHOLD = 1; +type PreviewCanvasSource = { + width: number; + height: number; + clientWidth?: number; + clientHeight?: number; +}; function buildBlurPolygonClipPath(points: Array<{ x: number; y: number }>) { if (points.length < 3) return undefined; @@ -36,6 +48,8 @@ interface AnnotationOverlayProps { onClick: (id: string) => void; zIndex: number; isSelectedBoost: boolean; // Boost z-index when selected for easy editing + previewSourceCanvas?: PreviewCanvasSource | null; + previewFrameVersion?: number; } export function AnnotationOverlay({ @@ -50,11 +64,13 @@ export function AnnotationOverlay({ onClick, zIndex, isSelectedBoost, + previewSourceCanvas, + previewFrameVersion, }: AnnotationOverlayProps) { - const x = (annotation.position.x / 100) * containerWidth; - const y = (annotation.position.y / 100) * containerHeight; - const width = (annotation.size.width / 100) * containerWidth; - const height = (annotation.size.height / 100) * containerHeight; + const committedX = (annotation.position.x / 100) * containerWidth; + const committedY = (annotation.position.y / 100) * containerHeight; + const committedWidth = (annotation.size.width / 100) * containerWidth; + const committedHeight = (annotation.size.height / 100) * containerHeight; const blurShape = annotation.type === "blur" ? (annotation.blurData?.shape ?? "rectangle") : null; const isSelectedFreehandBlur = isSelected && blurShape === "freehand"; const isDraggingRef = useRef(false); @@ -65,6 +81,108 @@ export function AnnotationOverlay({ [], ); const [livePointerPoint, setLivePointerPoint] = useState<{ x: number; y: number } | null>(null); + const mosaicCanvasRef = useRef(null); + const blurType = annotation.type === "blur" ? (annotation.blurData?.type ?? "blur") : "blur"; + const blurOverlayColor = + annotation.type === "blur" ? getBlurOverlayColor(annotation.blurData) : ""; + const mosaicGridOverlayColor = + annotation.type === "blur" ? getMosaicGridOverlayColor(annotation.blurData) : ""; + const [liveRect, setLiveRect] = useState({ + x: committedX, + y: committedY, + width: committedWidth, + height: committedHeight, + }); + + useEffect(() => { + setLiveRect({ + x: committedX, + y: committedY, + width: committedWidth, + height: committedHeight, + }); + }, [committedHeight, committedWidth, committedX, committedY]); + + const { x, y, width, height } = liveRect; + + useEffect(() => { + if (annotation.type !== "blur" || blurType !== "mosaic") { + return; + } + void previewFrameVersion; + + const canvas = mosaicCanvasRef.current; + const sourceCanvas = previewSourceCanvas; + if (!canvas || !sourceCanvas) { + return; + } + + const sourceWidth = sourceCanvas.width; + const sourceHeight = sourceCanvas.height; + const sourceClientWidth = sourceCanvas.clientWidth || containerWidth || sourceWidth; + const sourceClientHeight = sourceCanvas.clientHeight || containerHeight || sourceHeight; + if ( + sourceWidth <= 0 || + sourceHeight <= 0 || + sourceClientWidth <= 0 || + sourceClientHeight <= 0 + ) { + return; + } + + const drawWidth = Math.max(1, Math.round(width)); + const drawHeight = Math.max(1, Math.round(height)); + if (drawWidth <= 0 || drawHeight <= 0) { + return; + } + + canvas.width = drawWidth; + canvas.height = drawHeight; + + const context = canvas.getContext("2d", { willReadFrequently: true }); + if (!context) { + return; + } + + const scaleX = sourceWidth / sourceClientWidth; + const scaleY = sourceHeight / sourceClientHeight; + const sourceX = Math.max(0, Math.floor(x * scaleX)); + const sourceY = Math.max(0, Math.floor(y * scaleY)); + const sourceSampleWidth = Math.max(1, Math.ceil(drawWidth * scaleX)); + const sourceSampleHeight = Math.max(1, Math.ceil(drawHeight * scaleY)); + const clampedSampleWidth = Math.max(1, Math.min(sourceSampleWidth, sourceWidth - sourceX)); + const clampedSampleHeight = Math.max(1, Math.min(sourceSampleHeight, sourceHeight - sourceY)); + const blockSize = getNormalizedMosaicBlockSize(annotation.blurData); + const downscaledWidth = Math.max(1, Math.round(drawWidth / blockSize)); + const downscaledHeight = Math.max(1, Math.round(drawHeight / blockSize)); + canvas.width = downscaledWidth; + canvas.height = downscaledHeight; + + context.clearRect(0, 0, downscaledWidth, downscaledHeight); + context.imageSmoothingEnabled = true; + context.drawImage( + sourceCanvas as CanvasImageSource, + sourceX, + sourceY, + clampedSampleWidth, + clampedSampleHeight, + 0, + 0, + downscaledWidth, + downscaledHeight, + ); + }, [ + annotation, + blurType, + containerHeight, + containerWidth, + height, + previewFrameVersion, + previewSourceCanvas, + width, + x, + y, + ]); const renderArrow = () => { const direction = annotation.figureData?.arrowDirection || "right"; @@ -240,6 +358,10 @@ export function AnnotationOverlay({ 1, Math.round(annotation.blurData?.intensity ?? DEFAULT_BLUR_INTENSITY), ); + const blockSize = Math.max( + 1, + Math.round(annotation.blurData?.blockSize ?? DEFAULT_BLUR_BLOCK_SIZE), + ); const activeFreehandPoints = shape === "freehand" ? isFreehandDrawing @@ -292,12 +414,43 @@ export function AnnotationOverlay({ className="absolute inset-0" style={{ ...shapeMaskStyle, - backdropFilter: `blur(${blurIntensity}px)`, - WebkitBackdropFilter: `blur(${blurIntensity}px)`, - backgroundColor: "rgba(255, 255, 255, 0.02)", + backdropFilter: blurType === "mosaic" ? "none" : `blur(${blurIntensity}px)`, + WebkitBackdropFilter: blurType === "mosaic" ? "none" : `blur(${blurIntensity}px)`, + backgroundColor: blurOverlayColor, opacity: shouldShowFreehandBlurFill ? 1 : 0, }} /> + {blurType === "mosaic" && shouldShowFreehandBlurFill && ( + + )} + {blurType === "mosaic" && shouldShowFreehandBlurFill && ( +
+ )} + {blurType === "mosaic" && ( +
+ )} {isSelected && shape !== "freehand" && (
{ isDraggingRef.current = true; }} + onDrag={(_e, d) => { + setLiveRect((prev) => ({ + ...prev, + x: d.x, + y: d.y, + })); + }} onDragStop={(_e, d) => { + setLiveRect((prev) => ({ + ...prev, + x: d.x, + y: d.y, + })); const xPercent = (d.x / containerWidth) * 100; const yPercent = (d.y / containerHeight) * 100; onPositionChange(annotation.id, { x: xPercent, y: yPercent }); @@ -364,7 +529,21 @@ export function AnnotationOverlay({ isDraggingRef.current = false; }, 100); }} + onResize={(_e, _direction, ref, _delta, position) => { + setLiveRect({ + x: position.x, + y: position.y, + width: ref.offsetWidth, + height: ref.offsetHeight, + }); + }} onResizeStop={(_e, _direction, ref, _delta, position) => { + setLiveRect({ + x: position.x, + y: position.y, + width: ref.offsetWidth, + height: ref.offsetHeight, + }); const xPercent = (position.x / containerWidth) * 100; const yPercent = (position.y / containerHeight) * 100; const widthPercent = (ref.offsetWidth / containerWidth) * 100; diff --git a/src/components/video-editor/BlurSettingsPanel.tsx b/src/components/video-editor/BlurSettingsPanel.tsx index 382cd80..09bfe3a 100644 --- a/src/components/video-editor/BlurSettingsPanel.tsx +++ b/src/components/video-editor/BlurSettingsPanel.tsx @@ -1,14 +1,26 @@ import { Info, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Slider } from "@/components/ui/slider"; import { useScopedT } from "@/contexts/I18nContext"; +import { getBlurOverlayColor } from "@/lib/blurEffects"; import { cn } from "@/lib/utils"; import { type AnnotationRegion, + type BlurColor, type BlurData, type BlurShape, + DEFAULT_BLUR_BLOCK_SIZE, DEFAULT_BLUR_DATA, + MAX_BLUR_BLOCK_SIZE, MAX_BLUR_INTENSITY, + MIN_BLUR_BLOCK_SIZE, MIN_BLUR_INTENSITY, } from "./types"; @@ -31,6 +43,10 @@ export function BlurSettingsPanel({ { value: "rectangle", labelKey: "blurShapeRectangle" }, { value: "oval", labelKey: "blurShapeOval" }, ]; + const blurColorOptions: Array<{ value: BlurColor; labelKey: string }> = [ + { value: "white", labelKey: "blurColorWhite" }, + { value: "black", labelKey: "blurColorBlack" }, + ]; return (
@@ -91,27 +107,116 @@ export function BlurSettingsPanel({ })}
+
+ + +
+ +
+ +
+ {blurColorOptions.map((option) => { + const activeColor = blurRegion.blurData?.color ?? DEFAULT_BLUR_DATA.color; + const isActive = activeColor === option.value; + return ( + + ); + })} +
+
+
- {t("annotation.blurIntensity")} + {blurRegion.blurData?.type === "mosaic" + ? t("annotation.mosaicBlockSize") + : t("annotation.blurIntensity")} - {Math.round(blurRegion.blurData?.intensity ?? DEFAULT_BLUR_DATA.intensity)}px + {Math.round( + blurRegion.blurData?.type === "mosaic" + ? (blurRegion.blurData?.blockSize ?? DEFAULT_BLUR_BLOCK_SIZE) + : (blurRegion.blurData?.intensity ?? DEFAULT_BLUR_DATA.intensity), + )} + px
{ onBlurDataChange({ ...DEFAULT_BLUR_DATA, ...blurRegion.blurData, - intensity: values[0], + ...(blurRegion.blurData?.type === "mosaic" + ? { blockSize: values[0] } + : { intensity: values[0] }), }); }} onValueCommit={() => onBlurDataCommit?.()} - min={MIN_BLUR_INTENSITY} - max={MAX_BLUR_INTENSITY} + min={blurRegion.blurData?.type === "mosaic" ? MIN_BLUR_BLOCK_SIZE : MIN_BLUR_INTENSITY} + max={blurRegion.blurData?.type === "mosaic" ? MAX_BLUR_BLOCK_SIZE : MAX_BLUR_INTENSITY} step={1} className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" /> diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index ea477c8..b798641 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1348,7 +1348,7 @@ const VideoPlayback = forwardRef( if (annotation.id === selectedAnnotationId) return true; const timeMs = Math.round(currentTime * 1000); - return timeMs >= annotation.startMs && timeMs <= annotation.endMs; + return timeMs >= annotation.startMs && timeMs < annotation.endMs; }); const filteredBlurRegions = (blurRegions || []).filter((blurRegion) => { @@ -1358,7 +1358,7 @@ const VideoPlayback = forwardRef( if (blurRegion.id === selectedBlurId) return true; const timeMs = Math.round(currentTime * 1000); - return timeMs >= blurRegion.startMs && timeMs <= blurRegion.endMs; + return timeMs >= blurRegion.startMs && timeMs < blurRegion.endMs; }); const sorted = [ @@ -1371,6 +1371,15 @@ const VideoPlayback = forwardRef( region: blurRegion, })), ].sort((a, b) => a.region.zIndex - b.region.zIndex); + const previewSnapshotCanvas = (() => { + const app = appRef.current; + if (!app?.renderer?.extract) return null; + try { + return app.renderer.extract.canvas(app.stage); + } catch { + return null; + } + })(); // Handle click-through cycling: when clicking same annotation, cycle to next const handleAnnotationClick = (clickedId: string) => { @@ -1404,7 +1413,7 @@ const VideoPlayback = forwardRef( `${Math.round(p.x)}_${Math.round(p.y)}`).join("-")}` + ? `${item.region.id}-${overlaySize.width}-${overlaySize.height}-${item.region.blurData?.type ?? "blur"}-${item.region.blurData?.shape ?? "rectangle"}-${item.region.blurData?.color ?? "white"}-${Math.round(item.region.blurData?.blockSize ?? 0)}-${Math.round(item.region.blurData?.intensity ?? 0)}-${(item.region.blurData?.freehandPoints ?? []).map((p) => `${Math.round(p.x)}_${Math.round(p.y)}`).join("-")}` : `${item.region.id}-${overlaySize.width}-${overlaySize.height}` } annotation={item.region} @@ -1438,6 +1447,8 @@ const VideoPlayback = forwardRef( ? item.region.id === selectedBlurId : item.region.id === selectedAnnotationId } + previewSourceCanvas={previewSnapshotCanvas} + previewFrameVersion={Math.round(currentTime * 1000)} /> )); })()} diff --git a/src/components/video-editor/projectPersistence.test.ts b/src/components/video-editor/projectPersistence.test.ts index 9a99ef7..14dc240 100644 --- a/src/components/video-editor/projectPersistence.test.ts +++ b/src/components/video-editor/projectPersistence.test.ts @@ -68,6 +68,75 @@ describe("projectPersistence media compatibility", () => { ).toBe("rectangle"); }); + it("normalizes blur region type and mosaic block size safely", () => { + const editor = normalizeProjectEditor({ + annotationRegions: [ + { + id: "annotation-1", + startMs: 0, + endMs: 500, + type: "blur", + content: "", + position: { x: 10, y: 10 }, + size: { width: 20, height: 20 }, + style: { + color: "#fff", + backgroundColor: "transparent", + fontSize: 32, + fontFamily: "Inter", + fontWeight: "bold", + fontStyle: "normal", + textDecoration: "none", + textAlign: "center", + }, + zIndex: 1, + blurData: { + type: "mosaic", + shape: "rectangle", + color: "black", + intensity: 999, + blockSize: 999, + }, + }, + { + id: "annotation-2", + startMs: 0, + endMs: 500, + type: "blur", + content: "", + position: { x: 10, y: 10 }, + size: { width: 20, height: 20 }, + style: { + color: "#fff", + backgroundColor: "transparent", + fontSize: 32, + fontFamily: "Inter", + fontWeight: "bold", + fontStyle: "normal", + textDecoration: "none", + textAlign: "center", + }, + zIndex: 2, + blurData: { + type: "invalid" as never, + shape: "rectangle", + color: "invalid" as never, + intensity: 10, + blockSize: 0, + }, + }, + ], + }); + + expect(editor.annotationRegions[0].blurData?.type).toBe("mosaic"); + expect(editor.annotationRegions[0].blurData?.color).toBe("black"); + expect(editor.annotationRegions[0].blurData?.intensity).toBe(40); + expect(editor.annotationRegions[0].blurData?.blockSize).toBe(48); + expect(editor.annotationRegions[1].blurData?.type).toBe("blur"); + expect(editor.annotationRegions[1].blurData?.color).toBe("white"); + expect(editor.annotationRegions[1].blurData?.blockSize).toBe(4); + }); + it("accepts the dual frame webcam layout preset", () => { expect(normalizeProjectEditor({ webcamLayoutPreset: "dual-frame" }).webcamLayoutPreset).toBe( "dual-frame", diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index a8362c8..c085e0d 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -1,3 +1,4 @@ +import { normalizeBlurColor, normalizeBlurType } from "@/lib/blurEffects"; import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter"; import type { ProjectMedia } from "@/lib/recordingSession"; import { normalizeProjectMedia } from "@/lib/recordingSession"; @@ -9,6 +10,7 @@ import { DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, + DEFAULT_BLUR_BLOCK_SIZE, DEFAULT_BLUR_DATA, DEFAULT_BLUR_FREEHAND_POINTS, DEFAULT_BLUR_INTENSITY, @@ -20,8 +22,10 @@ import { DEFAULT_WEBCAM_POSITION, DEFAULT_WEBCAM_SIZE_PRESET, DEFAULT_ZOOM_DEPTH, + MAX_BLUR_BLOCK_SIZE, MAX_BLUR_INTENSITY, MAX_PLAYBACK_SPEED, + MIN_BLUR_BLOCK_SIZE, MIN_BLUR_INTENSITY, MIN_PLAYBACK_SPEED, type SpeedRegion, @@ -305,6 +309,8 @@ export function normalizeProjectEditor(editor: Partial): Pro VALID_BLUR_SHAPES.has(region.blurData.shape) ? region.blurData.shape : DEFAULT_BLUR_DATA.shape; + const blurType = normalizeBlurType(region.blurData?.type); + const blurColor = normalizeBlurColor(region.blurData?.color); return { id: region.id, @@ -365,10 +371,15 @@ export function normalizeProjectEditor(editor: Partial): Pro ? { ...DEFAULT_BLUR_DATA, ...region.blurData, + type: blurType, shape: blurShape, + color: blurColor, intensity: isFiniteNumber(region.blurData.intensity) ? clamp(region.blurData.intensity, MIN_BLUR_INTENSITY, MAX_BLUR_INTENSITY) : DEFAULT_BLUR_INTENSITY, + blockSize: isFiniteNumber(region.blurData.blockSize) + ? clamp(region.blurData.blockSize, MIN_BLUR_BLOCK_SIZE, MAX_BLUR_BLOCK_SIZE) + : DEFAULT_BLUR_BLOCK_SIZE, freehandPoints: Array.isArray(region.blurData.freehandPoints) ? region.blurData.freehandPoints .filter( diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index 609d38b..87e4331 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -68,14 +68,22 @@ export interface FigureData { } export type BlurShape = "rectangle" | "oval" | "freehand"; +export type BlurType = "blur" | "mosaic"; +export type BlurColor = "white" | "black"; export const MIN_BLUR_INTENSITY = 2; export const MAX_BLUR_INTENSITY = 40; export const DEFAULT_BLUR_INTENSITY = 12; +export const MIN_BLUR_BLOCK_SIZE = 4; +export const MAX_BLUR_BLOCK_SIZE = 48; +export const DEFAULT_BLUR_BLOCK_SIZE = 12; export interface BlurData { + type: BlurType; shape: BlurShape; + color: BlurColor; intensity: number; + blockSize: number; // Points are normalized (0-100) within the annotation bounds. freehandPoints?: Array<{ x: number; y: number }>; } @@ -157,8 +165,11 @@ export const DEFAULT_BLUR_FREEHAND_POINTS: Array<{ x: number; y: number }> = [ ]; export const DEFAULT_BLUR_DATA: BlurData = { + type: "blur", shape: "rectangle", + color: "white", intensity: DEFAULT_BLUR_INTENSITY, + blockSize: DEFAULT_BLUR_BLOCK_SIZE, freehandPoints: DEFAULT_BLUR_FREEHAND_POINTS, }; diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 7703d12..00e7c08 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -126,8 +126,15 @@ "arrowDirection": "Arrow Direction", "strokeWidth": "Stroke Width: {{width}}px", "arrowColor": "Arrow Color", + "blurType": "Blur Type", + "blurTypeBlur": "Blur", + "blurTypeMosaic": "Mosaic Blur", + "blurColor": "Blur Color", + "blurColorWhite": "White", + "blurColorBlack": "Black", "blurShape": "Blur Shape", "blurIntensity": "Blur Intensity", + "mosaicBlockSize": "Mosaic Block Size", "blurShapeRectangle": "Rectangle", "blurShapeOval": "Oval", "blurShapeFreehand": "Freehand", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 8dffa2e..92160bd 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -126,8 +126,15 @@ "arrowDirection": "Dirección de la flecha", "strokeWidth": "Grosor del trazo: {{width}}px", "arrowColor": "Color de la flecha", + "blurType": "Tipo de desenfoque", + "blurTypeBlur": "Desenfoque", + "blurTypeMosaic": "Desenfoque mosaico", + "blurColor": "Color del desenfoque", + "blurColorWhite": "Blanco", + "blurColorBlack": "Negro", "blurShape": "Forma del desenfoque", "blurIntensity": "Intensidad del desenfoque", + "mosaicBlockSize": "Tamano del bloque mosaico", "blurShapeRectangle": "Rectángulo", "blurShapeOval": "Óvalo", "blurShapeFreehand": "Mano alzada", diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index 381094f..ae98a59 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -115,8 +115,15 @@ "arrowDirection": "Direction de la flèche", "strokeWidth": "Épaisseur du trait : {{width}}px", "arrowColor": "Couleur de la flèche", + "blurType": "Type de flou", + "blurTypeBlur": "Flou", + "blurTypeMosaic": "Flou mosaique", + "blurColor": "Couleur du flou", + "blurColorWhite": "Blanc", + "blurColorBlack": "Noir", "blurShape": "Forme du flou", "blurIntensity": "Intensité du flou", + "mosaicBlockSize": "Taille des blocs de mosaique", "blurShapeRectangle": "Rectangle", "blurShapeOval": "Ovale", "blurShapeFreehand": "Main levée", diff --git a/src/lib/blurEffects.test.ts b/src/lib/blurEffects.test.ts new file mode 100644 index 0000000..4797e69 --- /dev/null +++ b/src/lib/blurEffects.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; +import { applyMosaicToImageData, getBlurOverlayColor, normalizeBlurColor } from "./blurEffects"; + +function createTestImageData(width: number, height: number) { + const data = new Uint8ClampedArray(width * height * 4); + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const offset = (y * width + x) * 4; + data[offset] = x * 20 + y; + data[offset + 1] = y * 20 + x; + data[offset + 2] = (x + y) * 10; + data[offset + 3] = 255; + } + } + + return { + data, + width, + height, + } as ImageData; +} + +describe("applyMosaicToImageData", () => { + it("collapses each block to a single representative color", () => { + const imageData = createTestImageData(4, 4); + const original = new Uint8ClampedArray(imageData.data); + + applyMosaicToImageData(imageData, 2); + + const topLeft = Array.from(imageData.data.slice(0, 4)); + const topRightOffset = (1 * 4 + 1) * 4; + const topRight = Array.from(imageData.data.slice(topRightOffset, topRightOffset + 4)); + expect(topLeft).toEqual(topRight); + + expect(Array.from(original.slice(0, 4))).not.toEqual(topLeft); + }); + + it("reduces unique pixel colors, making the transform information-lossy", () => { + const imageData = createTestImageData(8, 8); + const before = new Set(); + const after = new Set(); + + for (let i = 0; i < imageData.data.length; i += 4) { + before.add( + `${imageData.data[i]}-${imageData.data[i + 1]}-${imageData.data[i + 2]}-${imageData.data[i + 3]}`, + ); + } + + applyMosaicToImageData(imageData, 4); + + for (let i = 0; i < imageData.data.length; i += 4) { + after.add( + `${imageData.data[i]}-${imageData.data[i + 1]}-${imageData.data[i + 2]}-${imageData.data[i + 3]}`, + ); + } + + expect(after.size).toBeLessThan(before.size); + expect(after.size).toBe(4); + }); +}); + +describe("blur color helpers", () => { + it("normalizes invalid blur colors to white", () => { + expect(normalizeBlurColor("black")).toBe("black"); + expect(normalizeBlurColor("invalid")).toBe("white"); + }); + + it("returns a dark overlay when black blur color is selected", () => { + expect( + getBlurOverlayColor({ + type: "blur", + shape: "rectangle", + color: "black", + intensity: 12, + blockSize: 12, + }), + ).toBe("rgba(0, 0, 0, 0.18)"); + }); +}); diff --git a/src/lib/blurEffects.ts b/src/lib/blurEffects.ts new file mode 100644 index 0000000..6933924 --- /dev/null +++ b/src/lib/blurEffects.ts @@ -0,0 +1,113 @@ +import { + type BlurColor, + type BlurData, + type BlurType, + DEFAULT_BLUR_BLOCK_SIZE, + DEFAULT_BLUR_INTENSITY, + MAX_BLUR_BLOCK_SIZE, + MAX_BLUR_INTENSITY, + MIN_BLUR_BLOCK_SIZE, + MIN_BLUR_INTENSITY, +} from "@/components/video-editor/types"; + +function clamp(value: number, min: number, max: number) { + if (!Number.isFinite(value)) return min; + return Math.min(max, Math.max(min, value)); +} + +export function normalizeBlurType(value: unknown): BlurType { + return value === "mosaic" ? "mosaic" : "blur"; +} + +export function normalizeBlurColor(value: unknown): BlurColor { + return value === "black" ? "black" : "white"; +} + +export function getNormalizedBlurIntensity(blurData?: BlurData | null): number { + return clamp( + blurData?.intensity ?? DEFAULT_BLUR_INTENSITY, + MIN_BLUR_INTENSITY, + MAX_BLUR_INTENSITY, + ); +} + +export function getNormalizedMosaicBlockSize(blurData?: BlurData | null, scaleFactor = 1): number { + const rawBlockSize = clamp( + blurData?.blockSize ?? DEFAULT_BLUR_BLOCK_SIZE, + MIN_BLUR_BLOCK_SIZE, + MAX_BLUR_BLOCK_SIZE, + ); + return Math.max(1, Math.round(rawBlockSize * Math.max(scaleFactor, 0.01))); +} + +export function getBlurOverlayColor(blurData?: BlurData | null): string { + const blurColor = normalizeBlurColor(blurData?.color); + const blurType = normalizeBlurType(blurData?.type); + + if (blurColor === "black") { + return blurType === "mosaic" ? "rgba(0, 0, 0, 0.72)" : "rgba(0, 0, 0, 0.56)"; + } + + return blurType === "mosaic" ? "rgba(255, 255, 255, 0.06)" : "rgba(255, 255, 255, 0.02)"; +} + +export function getMosaicGridOverlayColor(blurData?: BlurData | null): string { + return normalizeBlurColor(blurData?.color) === "black" + ? "rgba(255,255,255,0.05)" + : "rgba(255,255,255,0.04)"; +} + +export function applyMosaicToImageData(imageData: ImageData, blockSize: number): ImageData { + const width = imageData.width; + const height = imageData.height; + const data = imageData.data; + const normalizedBlockSize = Math.max(1, Math.floor(blockSize)); + + if (width <= 0 || height <= 0 || normalizedBlockSize <= 1) { + return imageData; + } + + for (let blockY = 0; blockY < height; blockY += normalizedBlockSize) { + for (let blockX = 0; blockX < width; blockX += normalizedBlockSize) { + const blockWidth = Math.min(normalizedBlockSize, width - blockX); + const blockHeight = Math.min(normalizedBlockSize, height - blockY); + const pixelCount = blockWidth * blockHeight; + + if (pixelCount <= 0) { + continue; + } + + let redTotal = 0; + let greenTotal = 0; + let blueTotal = 0; + let alphaTotal = 0; + + for (let y = blockY; y < blockY + blockHeight; y++) { + for (let x = blockX; x < blockX + blockWidth; x++) { + const offset = (y * width + x) * 4; + redTotal += data[offset]; + greenTotal += data[offset + 1]; + blueTotal += data[offset + 2]; + alphaTotal += data[offset + 3]; + } + } + + const averageRed = Math.round(redTotal / pixelCount); + const averageGreen = Math.round(greenTotal / pixelCount); + const averageBlue = Math.round(blueTotal / pixelCount); + const averageAlpha = Math.round(alphaTotal / pixelCount); + + for (let y = blockY; y < blockY + blockHeight; y++) { + for (let x = blockX; x < blockX + blockWidth; x++) { + const offset = (y * width + x) * 4; + data[offset] = averageRed; + data[offset + 1] = averageGreen; + data[offset + 2] = averageBlue; + data[offset + 3] = averageAlpha; + } + } + } + } + + return imageData; +} diff --git a/src/lib/exporter/annotationRenderer.ts b/src/lib/exporter/annotationRenderer.ts index ec663e8..b0c4948 100644 --- a/src/lib/exporter/annotationRenderer.ts +++ b/src/lib/exporter/annotationRenderer.ts @@ -1,10 +1,11 @@ +import { type AnnotationRegion, type ArrowDirection } from "@/components/video-editor/types"; import { - type AnnotationRegion, - type ArrowDirection, - DEFAULT_BLUR_INTENSITY, - MAX_BLUR_INTENSITY, - MIN_BLUR_INTENSITY, -} from "@/components/video-editor/types"; + applyMosaicToImageData, + getBlurOverlayColor, + getNormalizedBlurIntensity, + getNormalizedMosaicBlockSize, + normalizeBlurType, +} from "@/lib/blurEffects"; let blurScratchCanvas: HTMLCanvasElement | null = null; let blurScratchCtx: CanvasRenderingContext2D | null = null; @@ -151,15 +152,16 @@ function renderBlur( scaleFactor: number, ) { const canvas = ctx.canvas; - const configuredIntensity = annotation.blurData?.intensity ?? DEFAULT_BLUR_INTENSITY; + const blurType = normalizeBlurType(annotation.blurData?.type); + const blurRadius = Math.max( 1, - Math.round(clamp(configuredIntensity, MIN_BLUR_INTENSITY, MAX_BLUR_INTENSITY) * scaleFactor), + Math.round(getNormalizedBlurIntensity(annotation.blurData) * scaleFactor), ); - - // Sample pixels around the target shape too; without this padding, small blur regions - // lose intensity because the filter has no neighboring pixels to blend with. - const samplePadding = Math.max(2, Math.ceil(blurRadius * 2)); + const samplePadding = + blurType === "mosaic" + ? Math.max(0, Math.ceil(getNormalizedMosaicBlockSize(annotation.blurData, scaleFactor))) + : Math.max(2, Math.ceil(blurRadius * 2)); const sx = Math.max(0, Math.floor(x) - samplePadding); const sy = Math.max(0, Math.floor(y) - samplePadding); const ex = Math.min(canvas.width, Math.ceil(x + width) + samplePadding); @@ -179,19 +181,26 @@ function renderBlur( blurScratchCtx.clearRect(0, 0, sw, sh); blurScratchCtx.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh); + if (blurType === "mosaic") { + const imageData = blurScratchCtx.getImageData(0, 0, sw, sh); + applyMosaicToImageData( + imageData, + getNormalizedMosaicBlockSize(annotation.blurData, scaleFactor), + ); + blurScratchCtx.putImageData(imageData, 0, 0); + } + ctx.save(); drawBlurPath(ctx, annotation, x, y, width, height); ctx.clip(); - ctx.filter = `blur(${blurRadius}px)`; + ctx.filter = blurType === "mosaic" ? "none" : `blur(${blurRadius}px)`; ctx.drawImage(blurScratchCanvas, sx, sy); ctx.filter = "none"; + ctx.fillStyle = getBlurOverlayColor(annotation.blurData); + ctx.fillRect(sx, sy, sw, sh); ctx.restore(); } -function clamp(value: number, min: number, max: number) { - return Math.min(max, Math.max(min, value)); -} - function renderText( ctx: CanvasRenderingContext2D, annotation: AnnotationRegion, @@ -364,7 +373,7 @@ export async function renderAnnotations( ): Promise { // Filter active annotations at current time const activeAnnotations = annotations.filter( - (ann) => currentTimeMs >= ann.startMs && currentTimeMs <= ann.endMs, + (ann) => currentTimeMs >= ann.startMs && currentTimeMs < ann.endMs, ); // Sort by z-index (lower first, so higher z-index draws on top) From 64cdc0dd3c20dce9afa116e7d52f0967ea1554c1 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 13:33:13 -0500 Subject: [PATCH 056/152] feat: add Nix flake with dev shell, package, and NixOS/Home Manager modules Reproducible development environment for NixOS/Nix contributors: - Dev shell with Node 22, system Electron, Playwright, LD_LIBRARY_PATH for X11/Wayland/audio libs, activated automatically via direnv - buildNpmPackage derivation wrapping system Electron with desktop file and hicolor icons - NixOS module (programs.openscreen.enable) with xdg-desktop-portal - Home Manager module for per-user installation - Overlay for composing with other flakes Tested: nix flake show, nix develop, nix build, nixos-rebuild switch --- .envrc | 1 + .gitignore | 7 ++- flake.lock | 27 ++++++++++ flake.nix | 122 +++++++++++++++++++++++++++++++++++++++++++ nix/hm-module.nix | 36 +++++++++++++ nix/module.nix | 42 +++++++++++++++ nix/package.nix | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/hm-module.nix create mode 100644 nix/module.nix create mode 100644 nix/package.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 1f895bd..040cada 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,9 @@ test-results playwright-report/ # Vitest browser mode screenshots -__screenshots__/ \ No newline at end of file +__screenshots__/ + +# Nix +result +result-* +.direnv/ \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..77972fb --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1775710090, + "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4c1018dae018162ec878d42fec712642d214fdfa", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a44e9c7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,122 @@ +{ + description = "OpenScreen — desktop screen recorder with built-in editor"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + in + { + # -- Per-system outputs (packages, dev shells) -- + + packages = forAllSystems (pkgs: { + openscreen = pkgs.callPackage ./nix/package.nix { }; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + }); + + devShells = forAllSystems ( + pkgs: + let + electron = pkgs.electron; + + # Libraries Electron needs at runtime on Linux + runtimeLibs = with pkgs; [ + # X11 + libx11 + libxcomposite + libxdamage + libxext + libxfixes + libxrandr + libxtst + libxcb + libxshmfence + + # Wayland + wayland + + # GTK / UI toolkit + gtk3 + glib + pango + cairo + gdk-pixbuf + atk + at-spi2-atk + at-spi2-core + + # Graphics + mesa + libGL + libdrm + vulkan-loader + + # Networking / crypto (NSS for Chromium) + nss + nspr + + # Audio + alsa-lib + pipewire + pulseaudio + + # System + dbus + cups + expat + libnotify + libsecret + util-linux # libuuid + ]; + in + { + default = pkgs.mkShell { + packages = with pkgs; [ + nodejs_22 + electron + + # Native module compilation + python3 + pkg-config + gcc + + # Playwright browser tests + playwright-driver.browsers + ]; + + # Electron's prebuilt binary needs these at runtime + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; + + # Tell the npm `electron` package to use the Nix-provided binary + # instead of downloading its own. vite-plugin-electron respects this. + ELECTRON_OVERRIDE_DIST_PATH = "${electron}/lib/electron"; + + # Playwright browser path for test:browser / test:e2e + PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1"; + + shellHook = '' + echo "OpenScreen dev shell — node $(node --version), electron v$(electron --version 2>/dev/null | tr -d 'v')" + ''; + }; + } + ); + + # -- System-wide outputs (modules, overlay) -- + + overlays.default = final: _prev: { + openscreen = self.packages.${final.stdenv.hostPlatform.system}.openscreen; + }; + + nixosModules.default = import ./nix/module.nix self; + homeManagerModules.default = import ./nix/hm-module.nix self; + }; +} diff --git a/nix/hm-module.nix b/nix/hm-module.nix new file mode 100644 index 0000000..b04f827 --- /dev/null +++ b/nix/hm-module.nix @@ -0,0 +1,36 @@ +# Home Manager module for OpenScreen +# Usage in flake-based Home Manager config: +# +# inputs.openscreen.url = "github:siddharthvaddem/openscreen"; +# +# { inputs, ... }: { +# imports = [ inputs.openscreen.homeManagerModules.default ]; +# programs.openscreen.enable = true; +# } +self: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.openscreen; +in +{ + options.programs.openscreen = { + enable = lib.mkEnableOption "OpenScreen screen recorder"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + defaultText = lib.literalExpression "inputs.openscreen.packages.\${pkgs.stdenv.hostPlatform.system}.openscreen"; + description = "The OpenScreen package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = [ cfg.package ]; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..3282d2d --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,42 @@ +# NixOS module for OpenScreen +# Usage in flake-based NixOS config: +# +# inputs.openscreen.url = "github:siddharthvaddem/openscreen"; +# +# { inputs, ... }: { +# imports = [ inputs.openscreen.nixosModules.default ]; +# programs.openscreen.enable = true; +# } +self: +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.openscreen; +in +{ + options.programs.openscreen = { + enable = lib.mkEnableOption "OpenScreen screen recorder"; + + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.openscreen; + defaultText = lib.literalExpression "inputs.openscreen.packages.\${pkgs.stdenv.hostPlatform.system}.openscreen"; + description = "The OpenScreen package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + # Screen capture on Wayland requires xdg-desktop-portal. + # We enable the base portal; users should also enable a + # desktop-specific portal (e.g. xdg-desktop-portal-gtk, + # xdg-desktop-portal-hyprland) in their DE config. + xdg.portal.enable = lib.mkDefault true; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 0000000..489fa13 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,130 @@ +{ + lib, + buildNpmPackage, + nodejs_22, + electron, + makeWrapper, + makeDesktopItem, + copyDesktopItems, +}: + +buildNpmPackage { + nodejs = nodejs_22; + pname = "openscreen"; + version = "1.3.0"; + + src = + let + fs = lib.fileset; + maybe = fs.maybeMissing; + in + fs.toSource { + root = ../.; + fileset = fs.difference ../. ( + fs.unions [ + ../nix + ../flake.nix + ../flake.lock + (maybe ../release) + (maybe ../test-results) + (maybe ../playwright-report) + (maybe ../.github) + (maybe ../.vscode) + (maybe ../.idea) + (maybe ../.kiro) + (maybe ../.envrc) + (maybe ../.direnv) + (fs.fileFilter (file: file.hasExt "md") ../.) + ] + ); + }; + + npmDepsHash = "sha256-Pd6J9TuggA9vM4s/LjdoK4MoBEivSzAWc/G2+pFOM2U="; + + env.ELECTRON_SKIP_BINARY_DOWNLOAD = "1"; + + # electron-builder is not needed — we wrap system electron directly + npmFlags = [ "--ignore-scripts" ]; + makeCacheWritable = true; + + # vite-plugin-electron compiles electron/ sources into dist-electron/ + # tsconfig has noEmit — tsc is type-check only + buildPhase = '' + runHook preBuild + npx vite build + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p "$out/lib/openscreen" + + # Renderer build output (index.html, JS chunks, copied public/ assets) + cp -r dist "$out/lib/openscreen/" + + # Main process + preload (compiled by vite-plugin-electron) + cp -r dist-electron "$out/lib/openscreen/" + + # Package manifest (electron reads "main" field to find entry point) + cp package.json "$out/lib/openscreen/" + + # Strip devDependencies (electron, vitest, biome, playwright, etc.) + npm prune --omit=dev --no-save + cp -r node_modules "$out/lib/openscreen/" + + # Asset resolution: when app.isPackaged is false, the main process resolves + # assets at /public/assets/. Mirror the electron-builder + # extraResources layout so wallpapers load correctly. + mkdir -p "$out/lib/openscreen/public/assets" + cp -r public/wallpapers "$out/lib/openscreen/public/assets/wallpapers" + + # Wrap system electron with the app directory + mkdir -p "$out/bin" + makeWrapper "${electron}/bin/electron" "$out/bin/openscreen" \ + --add-flags "$out/lib/openscreen" \ + --set ELECTRON_IS_DEV 0 + + # Install icons to hicolor theme + for size in 16 24 32 48 64 128 256 512 1024; do + icon="icons/icons/png/''${size}x''${size}.png" + if [ -f "$icon" ]; then + install -Dm644 "$icon" \ + "$out/share/icons/hicolor/''${size}x''${size}/apps/openscreen.png" + fi + done + + runHook postInstall + ''; + + nativeBuildInputs = [ + makeWrapper + copyDesktopItems + ]; + + desktopItems = [ + (makeDesktopItem { + name = "openscreen"; + desktopName = "OpenScreen"; + genericName = "Screen Recorder"; + exec = "openscreen %U"; + icon = "openscreen"; + comment = "Desktop screen recorder with built-in editor"; + categories = [ + "AudioVideo" + "Video" + "Recorder" + ]; + startupWMClass = "Openscreen"; + terminal = false; + }) + ]; + + meta = { + description = "Desktop screen recorder with built-in editor"; + homepage = "https://github.com/siddharthvaddem/openscreen"; + license = lib.licenses.mit; + mainProgram = "openscreen"; + platforms = lib.platforms.linux; + }; +} From 456816ab2ef655447f71b9584c75772e8b41c602 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 17:55:43 -0500 Subject: [PATCH 057/152] fix(nix): correct Electron binary path to libexec/electron Electron 41.x in nixpkgs places the binary at libexec/electron/, not lib/electron/. Without this fix, npm run dev fails with ENOENT. --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index a44e9c7..7b2d328 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ # Tell the npm `electron` package to use the Nix-provided binary # instead of downloading its own. vite-plugin-electron respects this. - ELECTRON_OVERRIDE_DIST_PATH = "${electron}/lib/electron"; + ELECTRON_OVERRIDE_DIST_PATH = "${electron}/libexec/electron"; # Playwright browser path for test:browser / test:e2e PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; From f106cc683544d26ff42da21c425bb645733c554a Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 18:14:44 -0500 Subject: [PATCH 058/152] fix(nix): restrict package source to git-tracked files Replace denylist approach with gitTracked to exclude node_modules, dist, .git, and any other untracked artifacts from the derivation. Keeps the nix/flake/md exclusions as they are nix-only or non-source. --- nix/package.nix | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/nix/package.nix b/nix/package.nix index 489fa13..198d68c 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -16,24 +16,14 @@ buildNpmPackage { src = let fs = lib.fileset; - maybe = fs.maybeMissing; in fs.toSource { root = ../.; - fileset = fs.difference ../. ( + fileset = fs.difference (fs.gitTracked ../.) ( fs.unions [ ../nix ../flake.nix ../flake.lock - (maybe ../release) - (maybe ../test-results) - (maybe ../playwright-report) - (maybe ../.github) - (maybe ../.vscode) - (maybe ../.idea) - (maybe ../.kiro) - (maybe ../.envrc) - (maybe ../.direnv) (fs.fileFilter (file: file.hasExt "md") ../.) ] ); From 515baf1d84aba617f92b6ef4af6ce24909307224 Mon Sep 17 00:00:00 2001 From: Dopiz Date: Mon, 13 Apr 2026 17:19:45 +0800 Subject: [PATCH 059/152] feat: add zh-TW locale --- scripts/i18n-check.mjs | 2 +- src/i18n/config.ts | 2 +- src/i18n/locales/zh-CN/common.json | 4 +- src/i18n/locales/zh-TW/common.json | 29 +++++ src/i18n/locales/zh-TW/dialogs.json | 70 ++++++++++ src/i18n/locales/zh-TW/editor.json | 41 ++++++ src/i18n/locales/zh-TW/launch.json | 37 ++++++ src/i18n/locales/zh-TW/settings.json | 176 ++++++++++++++++++++++++++ src/i18n/locales/zh-TW/shortcuts.json | 37 ++++++ src/i18n/locales/zh-TW/timeline.json | 53 ++++++++ 10 files changed, 447 insertions(+), 4 deletions(-) create mode 100644 src/i18n/locales/zh-TW/common.json create mode 100644 src/i18n/locales/zh-TW/dialogs.json create mode 100644 src/i18n/locales/zh-TW/editor.json create mode 100644 src/i18n/locales/zh-TW/launch.json create mode 100644 src/i18n/locales/zh-TW/settings.json create mode 100644 src/i18n/locales/zh-TW/shortcuts.json create mode 100644 src/i18n/locales/zh-TW/timeline.json diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index ca73b23..476e0ed 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -11,7 +11,7 @@ import path from "node:path"; const LOCALES_DIR = path.resolve("src/i18n/locales"); const BASE_LOCALE = "en"; -const COMPARE_LOCALES = ["zh-CN", "es", "tr", "ko-KR"]; +const COMPARE_LOCALES = ["zh-CN", "zh-TW", "es", "tr", "ko-KR"]; function getKeys(obj, prefix = "") { const keys = []; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 0933569..c352c9a 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,5 +1,5 @@ export const DEFAULT_LOCALE = "en" as const; -export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", "fr", "tr", "ko-KR"] as const; +export const SUPPORTED_LOCALES = ["en", "zh-CN", "zh-TW", "es", "fr", "tr", "ko-KR"] as const; export const I18N_NAMESPACES = [ "common", "dialogs", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 9a3cc1c..d8bff69 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -23,7 +23,7 @@ "exitFullscreen": "退出全屏" }, "locale": { - "name": "中文", - "short": "中文" + "name": "简体中文", + "short": "简中" } } diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json new file mode 100644 index 0000000..971d9ab --- /dev/null +++ b/src/i18n/locales/zh-TW/common.json @@ -0,0 +1,29 @@ +{ + "actions": { + "cancel": "取消", + "save": "儲存", + "delete": "刪除", + "close": "關閉", + "share": "分享", + "done": "完成", + "open": "開啟", + "upload": "上傳", + "export": "匯出", + "file": "檔案", + "edit": "編輯", + "view": "檢視", + "window": "視窗", + "quit": "退出", + "stopRecording": "停止錄製" + }, + "playback": { + "play": "播放", + "pause": "暫停", + "fullscreen": "全螢幕", + "exitFullscreen": "退出全螢幕" + }, + "locale": { + "name": "繁體中文", + "short": "繁中" + } +} diff --git a/src/i18n/locales/zh-TW/dialogs.json b/src/i18n/locales/zh-TW/dialogs.json new file mode 100644 index 0000000..b582aba --- /dev/null +++ b/src/i18n/locales/zh-TW/dialogs.json @@ -0,0 +1,70 @@ +{ + "export": { + "complete": "匯出完成", + "yourFormatReady": "您的 {{format}} 已準備就緒", + "showInFolder": "在資料夾中顯示", + "finalizingVideo": "正在完成影片匯出...", + "compilingGifProgress": "正在編譯 GIF... {{progress}}%", + "compilingGifWait": "正在編譯 GIF... 這可能需要一些時間", + "takeMoment": "這可能需要一點時間...", + "failed": "匯出失敗", + "tryAgain": "請重試", + "finalizingVideoTitle": "正在完成影片", + "compilingGif": "正在編譯 GIF", + "exportingFormat": "正在匯出 {{format}}", + "compiling": "編譯中", + "renderingFrames": "渲染影格", + "processing": "處理中...", + "finalizing": "正在完成...", + "compilingStatus": "編譯中...", + "status": "狀態", + "format": "格式", + "frames": "影格", + "cancelExport": "取消匯出", + "savedSuccessfully": "{{format}} 儲存成功!" + }, + "tutorial": { + "triggerLabel": "剪輯功能說明", + "title": "剪輯功能說明", + "description": "了解如何剪掉影片中不需要的部分。", + "explanationBefore": "剪輯工具透過定義您要", + "remove": "移除", + "explanationMiddle": "——任何被", + "covered": "覆蓋", + "explanationAfter": "的紅色剪輯區域部分將在匯出時被剪掉。", + "visualExample": "示例演示", + "removed": "已移除", + "kept": "保留", + "part1": "第 1 部分", + "part2": "第 2 部分", + "part3": "第 3 部分", + "finalVideo": "最終影片", + "step1Title": "1. 添加剪輯", + "step1DescriptionBefore": "按", + "step1DescriptionAfter": "鍵或點擊剪刀圖示來標記要移除的片段。", + "step2Title": "2. 調整", + "step2Description": "拖動紅色區域的邊緣,精確覆蓋您要剪掉的部分。" + }, + "unsavedChanges": { + "title": "未儲存的變更", + "message": "您有未儲存的變更。", + "detail": "是否在關閉前儲存專案?", + "saveAndClose": "儲存並關閉", + "discardAndClose": "捨棄並關閉", + "loadProject": "載入專案…", + "saveProject": "儲存專案…", + "saveProjectAs": "專案另存新檔…" + }, + "fileDialogs": { + "saveGif": "儲存匯出的 GIF", + "saveVideo": "儲存匯出的影片", + "selectVideo": "選擇影片檔案", + "saveProject": "儲存 OpenScreen 專案", + "openProject": "開啟 OpenScreen 專案", + "gifImage": "GIF 圖片", + "mp4Video": "MP4 影片", + "videoFiles": "影片檔案", + "openscreenProject": "OpenScreen 專案", + "allFiles": "所有檔案" + } +} diff --git a/src/i18n/locales/zh-TW/editor.json b/src/i18n/locales/zh-TW/editor.json new file mode 100644 index 0000000..73a3f4e --- /dev/null +++ b/src/i18n/locales/zh-TW/editor.json @@ -0,0 +1,41 @@ +{ + "newRecording": { + "title": "返回錄影", + "description": "目前工作階段已儲存。", + "cancel": "取消", + "confirm": "確認" + }, + "errors": { + "noVideoLoaded": "未載入影片", + "videoNotReady": "影片未就緒", + "unableToDetermineSourcePath": "無法確定來源影片路徑", + "failedToSaveGif": "儲存 GIF 失敗", + "gifExportFailed": "GIF 匯出失敗", + "failedToSaveVideo": "儲存影片失敗", + "exportFailed": "匯出失敗", + "exportFailedWithError": "匯出失敗:{{error}}", + "failedToSaveExport": "儲存匯出檔案失敗", + "failedToSaveExportedVideo": "儲存匯出的影片失敗", + "failedToRevealInFolder": "在資料夾中顯示時出錯:{{error}}" + }, + "export": { + "canceled": "匯出已取消", + "exportedSuccessfully": "{{format}} 匯出成功" + }, + "project": { + "saveCanceled": "專案儲存已取消", + "failedToSave": "儲存專案失敗", + "savedTo": "專案已儲存至 {{path}}", + "failedToLoad": "載入專案失敗", + "invalidFormat": "無效的專案檔案格式", + "loadedFrom": "專案已從 {{path}} 載入" + }, + "recording": { + "failedCameraAccess": "請求攝影機權限失敗。", + "cameraBlocked": "攝影機權限已被封鎖。請在系統設定中啟用以使用攝影機。", + "systemAudioUnavailable": "系統音訊不可用。將在無系統音訊的情況下錄製。", + "microphoneDenied": "麥克風權限被拒絕。錄製將繼續,但不包含音訊。", + "cameraDenied": "攝影機權限被拒絕。錄製將繼續,但不包含攝影機畫面。", + "permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。" + } +} diff --git a/src/i18n/locales/zh-TW/launch.json b/src/i18n/locales/zh-TW/launch.json new file mode 100644 index 0000000..e8b723f --- /dev/null +++ b/src/i18n/locales/zh-TW/launch.json @@ -0,0 +1,37 @@ +{ + "tooltips": { + "hideHUD": "隱藏控制面板", + "closeApp": "關閉應用程式", + "restartRecording": "重新開始錄製", + "cancelRecording": "取消錄製", + "pauseRecording": "暫停錄製", + "resumeRecording": "繼續錄製", + "openVideoFile": "開啟影片檔案", + "openProject": "開啟專案" + }, + "audio": { + "enableSystemAudio": "啟用系統音訊", + "disableSystemAudio": "停用系統音訊", + "enableMicrophone": "啟用麥克風", + "disableMicrophone": "停用麥克風", + "defaultMicrophone": "預設麥克風" + }, + "webcam": { + "enableWebcam": "啟用攝影機", + "disableWebcam": "停用攝影機", + "defaultCamera": "預設攝影機", + "searching": "正在搜尋...", + "noneFound": "未找到攝影機", + "unavailable": "攝影機不可用" + }, + "sourceSelector": { + "loading": "正在載入來源...", + "screens": "螢幕 ({{count}})", + "windows": "視窗 ({{count}})", + "defaultSourceName": "螢幕" + }, + "recording": { + "selectSource": "請選擇要錄製的來源" + }, + "language": "語言" +} diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json new file mode 100644 index 0000000..6344a99 --- /dev/null +++ b/src/i18n/locales/zh-TW/settings.json @@ -0,0 +1,176 @@ +{ + "zoom": { + "level": "縮放級別", + "selectRegion": "選擇要調整的縮放區域", + "deleteZoom": "刪除縮放", + "focusMode": { + "title": "對焦模式", + "manual": "手動", + "auto": "自動", + "autoDescription": "攝影機跟隨錄製時的游標位置" + }, + "speed": { + "title": "縮放速度", + "instant": "即時", + "fast": "快速", + "smooth": "平滑", + "lazy": "緩慢" + } + }, + "speed": { + "playbackSpeed": "播放速度", + "selectRegion": "選擇要調整的速度區域", + "deleteRegion": "刪除速度區域", + "customPlaybackSpeed": "自訂播放速度", + "maxSpeedError": "速度不能超過 16×" + }, + "trim": { + "deleteRegion": "刪除剪輯區域" + }, + "layout": { + "title": "版面配置", + "preset": "預設", + "selectPreset": "選擇預設", + "pictureInPicture": "子母畫面", + "verticalStack": "垂直堆疊", + "dualFrame": "雙畫框", + "webcamShape": "攝影機形狀", + "webcamSize": "攝影機大小" + }, + "effects": { + "title": "影片效果", + "blurBg": "模糊背景", + "motionBlur": "動態模糊", + "off": "關", + "shadow": "陰影", + "roundness": "圓角", + "padding": "內邊距" + }, + "background": { + "title": "背景", + "image": "圖片", + "color": "顏色", + "gradient": "漸層", + "uploadCustom": "上傳自訂", + "gradientLabel": "漸層 {{index}}" + }, + "crop": { + "title": "裁剪", + "cropVideo": "裁剪影片", + "dragInstruction": "拖動每一側來調整裁剪區域", + "ratio": "比例", + "free": "自由", + "done": "完成", + "lockAspectRatio": "鎖定長寬比", + "unlockAspectRatio": "解鎖長寬比" + }, + "exportFormat": { + "mp4": "MP4", + "gif": "GIF", + "mp4Video": "MP4 影片", + "mp4Description": "高品質影片檔案", + "gifAnimation": "GIF 動畫", + "gifDescription": "可分享的動態圖片" + }, + "exportQuality": { + "title": "匯出品質", + "low": "低", + "medium": "中", + "high": "高" + }, + "gifSettings": { + "frameRate": "GIF 影格率", + "size": "GIF 尺寸", + "loop": "循環 GIF" + }, + "project": { + "save": "儲存專案", + "load": "載入專案" + }, + "export": { + "videoButton": "匯出影片", + "gifButton": "匯出 GIF", + "chooseSaveLocation": "選擇儲存位置" + }, + "links": { + "reportBug": "回報錯誤", + "starOnGithub": "在 GitHub 上加星" + }, + "imageUpload": { + "invalidFileType": "無效的檔案類型", + "jpgOnly": "請上傳 JPG 或 JPEG 格式的圖片檔案。", + "uploadSuccess": "自訂圖片上傳成功!", + "failedToUpload": "上傳圖片失敗", + "errorReading": "讀取檔案時出錯。" + }, + "annotation": { + "title": "標註設定", + "active": "啟用", + "typeText": "文字", + "typeImage": "圖片", + "typeArrow": "箭頭", + "typeBlur": "模糊", + "textContent": "文字內容", + "textPlaceholder": "輸入您的文字...", + "fontStyle": "字體樣式", + "selectStyle": "選擇樣式", + "size": "大小", + "customFonts": "自訂字體", + "textColor": "文字顏色", + "background": "背景", + "none": "無", + "color": "顏色", + "clearBackground": "清除背景", + "uploadImage": "上傳圖片", + "supportedFormats": "支援的格式:JPG、PNG、GIF、WebP", + "arrowDirection": "箭頭方向", + "strokeWidth": "描邊寬度:{{width}}px", + "arrowColor": "箭頭顏色", + "blurShape": "模糊形狀", + "blurIntensity": "模糊強度", + "blurShapeRectangle": "矩形", + "blurShapeOval": "橢圓", + "blurShapeFreehand": "自由手繪", + "deleteAnnotation": "刪除標註", + "shortcutsAndTips": "快捷鍵與提示", + "tipMovePlayhead": "將播放頭移動到重疊的標註區域並選擇一個項目。", + "tipTabCycle": "使用 Tab 鍵在重疊項目之間循環切換。", + "tipShiftTabCycle": "使用 Shift+Tab 反向循環切換。", + "invalidImageType": "無效的檔案類型", + "imageFormatsOnly": "請上傳 JPG、PNG、GIF 或 WebP 格式的圖片檔案。", + "imageUploadSuccess": "圖片上傳成功!", + "failedImageUpload": "上傳圖片失敗" + }, + "fontStyles": { + "classic": "經典", + "editor": "編輯器", + "strong": "粗體", + "typewriter": "打字機", + "deco": "裝飾", + "simple": "簡約", + "modern": "現代", + "clean": "簡潔" + }, + "customFont": { + "dialogTitle": "新增 Google 字體", + "urlLabel": "Google Fonts 匯入 URL", + "urlPlaceholder": "https://fonts.googleapis.com/css2?family=Roboto&display=swap", + "urlHelp": "從 Google Fonts 取得:選擇字體 → 點擊 \"Get font\" → 複製 @import URL", + "nameLabel": "顯示名稱", + "namePlaceholder": "我的自訂字體", + "nameHelp": "這是字體在字體選擇器中顯示的名稱", + "addButton": "新增字體", + "addingButton": "新增中...", + "errorEmptyUrl": "請輸入 Google Fonts 匯入 URL", + "errorInvalidUrl": "請輸入有效的 Google Fonts URL", + "errorEmptyName": "請輸入字體名稱", + "errorExtractFailed": "無法從 URL 中提取字體系列", + "successMessage": "字體 \"{{fontName}}\" 新增成功", + "failedToAdd": "新增字體失敗", + "errorTimeout": "字體載入時間過長。請檢查 URL 並重試。", + "errorLoadFailed": "無法載入該字體。請確認 Google Fonts URL 是否正確。" + }, + "language": { + "title": "語言" + } +} diff --git a/src/i18n/locales/zh-TW/shortcuts.json b/src/i18n/locales/zh-TW/shortcuts.json new file mode 100644 index 0000000..54c0cfc --- /dev/null +++ b/src/i18n/locales/zh-TW/shortcuts.json @@ -0,0 +1,37 @@ +{ + "title": "鍵盤快捷鍵", + "customize": "自訂", + "configurable": "可設定", + "fixed": "固定", + "pressKey": "請按下按鍵…", + "clickToChange": "點擊以變更", + "pressEscToCancel": "按 Esc 取消", + "helpText": "點擊一個快捷鍵,然後按下新的組合鍵。按 Esc 取消。", + "resetToDefaults": "還原預設設定", + "alreadyUsedBy": "已被 \"{{action}}\" 使用", + "swap": "交換", + "reservedShortcut": "此快捷鍵已保留給 \"{{label}}\",無法重新指定。", + "savedToast": "鍵盤快捷鍵已儲存", + "resetToast": "已還原預設快捷鍵 — 點擊儲存以套用", + "actions": { + "addZoom": "新增縮放", + "addTrim": "新增剪輯", + "addSpeed": "新增速度", + "addAnnotation": "新增標註", + "addBlur": "新增模糊", + "addKeyframe": "新增關鍵影格", + "deleteSelected": "刪除所選", + "playPause": "播放 / 暫停" + }, + "fixedActions": { + "undo": "復原", + "redo": "重做", + "cycleAnnotationsForward": "向前切換標註", + "cycleAnnotationsBackward": "向後切換標註", + "deleteSelectedAlt": "刪除所選(替代)", + "panTimeline": "平移時間軸", + "zoomTimeline": "縮放時間軸", + "frameBack": "上一影格", + "frameForward": "下一影格" + } +} diff --git a/src/i18n/locales/zh-TW/timeline.json b/src/i18n/locales/zh-TW/timeline.json new file mode 100644 index 0000000..52457d6 --- /dev/null +++ b/src/i18n/locales/zh-TW/timeline.json @@ -0,0 +1,53 @@ +{ + "buttons": { + "addZoom": "新增縮放 (Z)", + "suggestZooms": "根據游標建議縮放", + "addTrim": "新增剪輯 (T)", + "addAnnotation": "新增標註 (A)", + "addSpeed": "新增速度 (S)", + "addBlur": "新增模糊 (B)" + }, + "hints": { + "pressZoom": "按 Z 新增縮放", + "pressTrim": "按 T 新增剪輯", + "pressAnnotation": "按 A 新增標註", + "pressSpeed": "按 S 新增速度", + "pressBlur": "按 B 新增模糊區域" + }, + "labels": { + "pan": "平移", + "zoom": "縮放", + "zoomItem": "縮放 {{index}}", + "trimItem": "剪輯 {{index}}", + "speedItem": "速度 {{index}}", + "annotationItem": "標註", + "imageItem": "圖片", + "emptyText": "空文字", + "blurItem": "模糊 {{index}}" + }, + "emptyState": { + "noVideo": "未載入影片", + "dragAndDrop": "拖放影片以開始編輯" + }, + "errors": { + "cannotPlaceZoom": "無法在此處放置縮放", + "zoomExistsAtLocation": "此位置已存在縮放或沒有足夠的空間。", + "zoomSuggestionUnavailable": "縮放建議處理器不可用", + "noCursorTelemetry": "無可用的游標遙測資料", + "noCursorTelemetryDescription": "請先錄製一段螢幕錄影以產生基於游標的建議。", + "noUsableTelemetry": "無可用的游標遙測資料", + "noUsableTelemetryDescription": "錄製內容沒有包含足夠的游標移動資料。", + "noDwellMoments": "未找到明確的游標停留時刻", + "noDwellMomentsDescription": "請嘗試在重要操作上進行較慢游標停留的錄製。", + "noAutoZoomSlots": "無可用的自動縮放位置", + "noAutoZoomSlotsDescription": "偵測到的停留點與現有縮放區域重疊。", + "cannotPlaceTrim": "無法在此處放置剪輯", + "trimExistsAtLocation": "此位置已存在剪輯或沒有足夠的空間。", + "cannotPlaceSpeed": "無法在此處放置速度", + "speedExistsAtLocation": "此位置已存在速度區域或沒有足夠的空間。" + }, + "success": { + "addedZoomSuggestions": "已新增 {{count}} 個基於游標的縮放建議", + "addedZoomSuggestionsPlural": "已新增 {{count}} 個基於游標的縮放建議" + } +} From d20a062150f3520b25233875b9b73a70d51c6723 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Mon, 13 Apr 2026 06:17:07 -0500 Subject: [PATCH 060/152] fix(nix): handle store path sources for path: flake inputs gitTracked uses builtins.fetchGit which fails when the source is already a store path (happens with path: flake inputs from consuming flakes). Detect store paths at eval time and fall back to cleanSource. --- nix/package.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index 198d68c..195043f 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -16,10 +16,14 @@ buildNpmPackage { src = let fs = lib.fileset; + # gitTracked fails when source is already a store path (path: flake inputs). + # Detect this and fall back to cleanSource which handles both cases. + isStorePath = builtins.storeDir == builtins.substring 0 (builtins.stringLength builtins.storeDir) (toString ../.); + baseFiles = if isStorePath then fs.fromSource (lib.cleanSource ../.) else fs.gitTracked ../.; in fs.toSource { root = ../.; - fileset = fs.difference (fs.gitTracked ../.) ( + fileset = fs.difference baseFiles ( fs.unions [ ../nix ../flake.nix From 46c611bd3fc34e26a013d7171d67aa32edd133b8 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Mon, 13 Apr 2026 17:30:16 +0200 Subject: [PATCH 061/152] fix: include epsilon subtration in totalFrame calculation --- src/lib/exporter/streamingDecoder.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index c028832..25a6aa2 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -2,7 +2,7 @@ import { WebDemuxer } from "web-demuxer"; import type { SpeedRegion, TrimRegion } from "@/components/video-editor/types"; const SOURCE_LOAD_TIMEOUT_MS = 60_000; - +const EPSILON_SEC = 0.001; /** * Build a full WebCodecs-compatible AV1 codec string from the AV1CodecConfigurationRecord. * web-demuxer may return a bare "av01" when the WASM-side parser fails to read @@ -249,7 +249,6 @@ export class StreamingVideoDecoder { Math.ceil(((segment.endSec - segment.startSec) / segment.speed) * targetFrameRate), ); const frameDurationUs = 1_000_000 / targetFrameRate; - const epsilonSec = 0.001; // Async frame queue — decoder pushes, consumer pulls const pendingFrames: VideoFrame[] = []; @@ -360,7 +359,7 @@ export class StreamingVideoDecoder { const sourceTimeSec = segment.startSec + (segmentFrameIndex / targetFrameRate) * segment.speed; - if (sourceTimeSec >= segment.endSec - epsilonSec) return false; + if (sourceTimeSec >= segment.endSec - EPSILON_SEC) return false; const clone = new VideoFrame(heldFrame, { timestamp: heldFrame.timestamp }); await onFrame(clone, exportFrameIndex * frameDurationUs, sourceTimeSec * 1000); @@ -379,7 +378,7 @@ export class StreamingVideoDecoder { // Finalize completed segments before handling this frame. while ( segmentIdx < segments.length && - frameTimeSec >= segments[segmentIdx].endSec - epsilonSec + frameTimeSec >= segments[segmentIdx].endSec - EPSILON_SEC ) { const segment = segments[segmentIdx]; while (!this.cancelled && (await emitHeldFrameForTarget(segment))) { @@ -391,7 +390,7 @@ export class StreamingVideoDecoder { if ( heldFrame && segmentIdx < segments.length && - heldFrameSec < segments[segmentIdx].startSec - epsilonSec + heldFrameSec < segments[segmentIdx].startSec - EPSILON_SEC ) { heldFrame.close(); heldFrame = null; @@ -406,7 +405,7 @@ export class StreamingVideoDecoder { const currentSegment = segments[segmentIdx]; // Before current segment (trimmed region or pre-roll). - if (frameTimeSec < currentSegment.startSec - epsilonSec) { + if (frameTimeSec < currentSegment.startSec - EPSILON_SEC) { frame.close(); continue; } @@ -427,7 +426,7 @@ export class StreamingVideoDecoder { const sourceTimeSec = currentSegment.startSec + (segmentFrameIndex / targetFrameRate) * currentSegment.speed; - if (sourceTimeSec >= currentSegment.endSec - epsilonSec) { + if (sourceTimeSec >= currentSegment.endSec - EPSILON_SEC) { break; } if (sourceTimeSec > handoffBoundarySec) { @@ -449,7 +448,7 @@ export class StreamingVideoDecoder { if (heldFrame && segmentIdx < segments.length) { while (!this.cancelled && segmentIdx < segments.length) { const segment = segments[segmentIdx]; - if (heldFrameSec < segment.startSec - epsilonSec) { + if (heldFrameSec < segment.startSec - EPSILON_SEC) { break; } @@ -461,7 +460,7 @@ export class StreamingVideoDecoder { segmentFrameIndex = 0; if ( segmentIdx < segments.length && - heldFrameSec < segments[segmentIdx].startSec - epsilonSec + heldFrameSec < segments[segmentIdx].startSec - EPSILON_SEC ) { break; } @@ -549,10 +548,10 @@ export class StreamingVideoDecoder { (sum, seg) => sum + (seg.endSec - seg.startSec) / seg.speed, 0, ), - totalFrames: segments.reduce( - (sum, seg) => sum + Math.ceil(((seg.endSec - seg.startSec) / seg.speed) * targetFrameRate), - 0, - ), + totalFrames: segments.reduce((sum, seg) => { + const segDur = seg.endSec - seg.startSec - EPSILON_SEC; + return sum + Math.max(0, Math.ceil((segDur / seg.speed) * targetFrameRate)); + }, 0), }; } From 6441e96035cd4355d757ac85dd01628b8969675a Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Tue, 14 Apr 2026 12:45:02 +0530 Subject: [PATCH 062/152] fix: prevent crash in read-binary-file handler and improve error debugging --- electron/ipc/handlers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 4cb4875..d0b42a3 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -501,8 +501,9 @@ export function registerIpcHandlers( }); ipcMain.handle("read-binary-file", async (_, inputPath: string) => { + let normalizedPath: string | null = null; try { - const normalizedPath = normalizeVideoSourcePath(inputPath); + normalizedPath = normalizeVideoSourcePath(inputPath); if (!normalizedPath) { return { success: false, message: "Invalid file path" }; } @@ -527,6 +528,7 @@ export function registerIpcHandlers( success: false, message: "Failed to read binary file", error: String(error), + path: normalizedPath, }; } }); From df06369b75d251253b44a4e4baca44664492c97b Mon Sep 17 00:00:00 2001 From: themaker Date: Tue, 14 Apr 2026 15:58:39 +0100 Subject: [PATCH 063/152] me and you --- shell.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 shell.sh diff --git a/shell.sh b/shell.sh new file mode 100644 index 0000000..e69de29 From 5bcdf4c558c28efe535ca8b05d1b9cc87c57b91d Mon Sep 17 00:00:00 2001 From: themaker Date: Tue, 14 Apr 2026 15:58:52 +0100 Subject: [PATCH 064/152] me and you --- shell.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 shell.sh diff --git a/shell.sh b/shell.sh deleted file mode 100644 index e69de29..0000000 From 14bbe8f18348a56a6c6373bbb4818f0c05c18874 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Tue, 14 Apr 2026 20:26:21 +0200 Subject: [PATCH 065/152] fix: algin frame cap with epsilon boundary to prevent frame count mismatch --- src/lib/exporter/streamingDecoder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 25a6aa2..651a557 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -246,7 +246,9 @@ export class StreamingVideoDecoder { speedRegions, ); const segmentOutputFrameCounts = segments.map((segment) => - Math.ceil(((segment.endSec - segment.startSec) / segment.speed) * targetFrameRate), + Math.ceil( + ((segment.endSec - segment.startSec - EPSILON_SEC) / segment.speed) * targetFrameRate, + ), ); const frameDurationUs = 1_000_000 / targetFrameRate; From 143cd1e7726e3d5c8f5bcb30f9bf03f8aa8fbfb9 Mon Sep 17 00:00:00 2001 From: themaker Date: Tue, 14 Apr 2026 23:17:06 +0100 Subject: [PATCH 066/152] additions --- .gitignore | 6 +- package-lock.json | 1248 --------------------------------------------- 2 files changed, 5 insertions(+), 1249 deletions(-) diff --git a/.gitignore b/.gitignore index 1f895bd..2b40ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ dist-ssr # Editor directories and files .vscode/* +.zed/ !.vscode/extensions.json .idea .DS_Store @@ -33,4 +34,7 @@ test-results playwright-report/ # Vitest browser mode screenshots -__screenshots__/ \ No newline at end of file +__screenshots__/ + +# shell files +./shell.sh diff --git a/package-lock.json b/package-lock.json index ba40beb..998f8f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4993,474 +4993,6 @@ } } }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", @@ -5487,630 +5019,6 @@ } } }, - "node_modules/@vitest/browser-playwright/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@vitest/browser-playwright/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@vitest/browser/node_modules/@vitest/mocker": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", @@ -6137,85 +5045,6 @@ } } }, - "node_modules/@vitest/browser/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/@vitest/browser/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@vitest/browser/node_modules/pixelmatch": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", @@ -6237,83 +5066,6 @@ "node": ">=14.19.0" } }, - "node_modules/@vitest/browser/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", From 566830a866b1fbbcb403e00c388a9ee8fe6ad56b Mon Sep 17 00:00:00 2001 From: themaker Date: Wed, 15 Apr 2026 09:39:03 +0100 Subject: [PATCH 067/152] feat(): changed .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2b40ccd..baaa2fe 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ playwright-report/ __screenshots__/ # shell files -./shell.sh +/shell.sh From 1cdb8ed1cd213a1e3632e9eac96ca8c87b022a06 Mon Sep 17 00:00:00 2001 From: themaker Date: Wed, 15 Apr 2026 14:25:30 +0100 Subject: [PATCH 068/152] feat(ui): add squircle corner shape to SourceSelector and polish sources spinner ui Added corner-shape: squircle; to SourceSelector.module.css for more visually appealing rounded corners. Customized windows source selector scrollbar to be more subtle but carry the product colour. Removed box-shadow on SourceSelector because electron doesn't round corners of the shadow, thereby leaving a square border shadow conflicting with the rounded corners of the SourceSelector. --- .../launch/SourceSelector.module.css | 35 ++++++++++--------- src/components/launch/SourceSelector.tsx | 22 ++++++------ src/index.css | 4 +++ 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/components/launch/SourceSelector.module.css b/src/components/launch/SourceSelector.module.css index 51239ac..9d0d580 100644 --- a/src/components/launch/SourceSelector.module.css +++ b/src/components/launch/SourceSelector.module.css @@ -2,15 +2,21 @@ background: linear-gradient(135deg, rgba(28, 28, 34, 0.92) 0%, rgba(18, 18, 22, 0.88) 100%); backdrop-filter: blur(20px) saturate(160%); -webkit-backdrop-filter: blur(20px) saturate(160%); - border-radius: 14px; - box-shadow: - 0 4px 16px 0 rgba(0, 0, 0, 0.32), - 0 1px 3px 0 rgba(0, 0, 0, 0.18) inset; - border: 1px solid rgba(60, 60, 80, 0.18); + border-radius: 30px; + corner-shape: squircle; + /* + Removed box-shadow here because electron doesn't round corners of the shadow, thereby leaving a square border shadow conflicting with the rounded corners of the SourceSelector. + The result is easily visible when you place a white window just behind the SourceSelector + */ + /*box-shadow: + 0 0px 16px 0 rgba(0, 0, 0, 0.32), + 0 1px 3px 0 rgba(0, 0, 0, 0.18) inset;*/ + border: 1.5px solid rgba(60, 60, 80, 0.3); } .sourceCard { - border-radius: 12px; + corner-shape: squircle; + border-radius: 20px; background: linear-gradient(120deg, rgba(38, 38, 48, 0.98) 0%, rgba(24, 24, 32, 0.96) 100%); border: 1px solid rgba(60, 60, 80, 0.22); box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.18); @@ -28,7 +34,7 @@ } .selected { - border: 2px solid #34b27b; + border: 1.5px solid #34b27b; background: linear-gradient(120deg, rgba(52, 178, 123, 0.08) 0%, rgba(38, 38, 48, 0.98) 100%); box-shadow: 0 0 12px rgba(52, 178, 123, 0.15), @@ -70,30 +76,27 @@ } /* scrollbar */ -.sourceGridScroll { - scrollbar-width: thin; - scrollbar-color: rgba(52, 178, 123, 0.5) rgba(40, 40, 50, 0.6); -} .sourceGridScroll::-webkit-scrollbar { - width: 8px; + width: 3px; } .sourceGridScroll::-webkit-scrollbar-track { - background: rgba(30, 30, 38, 0.5); + background: rgba(30, 30, 38, 0.3); border-radius: 4px; - margin: 4px 0; } .sourceGridScroll::-webkit-scrollbar-thumb { - background: rgba(80, 80, 100, 0.6); - border-radius: 4px; + background: rgba(52, 178, 123, 0.5); + border-radius: 10px; } .sourceGridScroll::-webkit-scrollbar-thumb:hover { background: rgba(52, 178, 123, 0.6); + cursor: grab; } .sourceGridScroll::-webkit-scrollbar-thumb:active { background: rgba(52, 178, 123, 0.8); + cursor: grabbing; } diff --git a/src/components/launch/SourceSelector.tsx b/src/components/launch/SourceSelector.tsx index 5768c3a..a2aec55 100644 --- a/src/components/launch/SourceSelector.tsx +++ b/src/components/launch/SourceSelector.tsx @@ -65,7 +65,7 @@ export function SourceSelector() { style={{ minHeight: "100vh" }} >
-
+

{t("sourceSelector.loading")}

@@ -84,10 +84,10 @@ export function SourceSelector() { {source.name} {isSelected && ( -
+
@@ -111,16 +111,16 @@ export function SourceSelector() { defaultValue={screenSources.length === 0 ? "windows" : "screens"} className="flex-1 flex flex-col" > - + {t("sourceSelector.screens", { count: String(screenSources.length) })} {t("sourceSelector.windows", { count: String(windowSources.length) })} @@ -128,14 +128,14 @@ export function SourceSelector() {
{screenSources.map(renderSourceCard)}
{windowSources.map(renderSourceCard)}
@@ -143,18 +143,18 @@ export function SourceSelector() {
-
+
diff --git a/src/index.css b/src/index.css index 74f5669..694f8bd 100644 --- a/src/index.css +++ b/src/index.css @@ -88,6 +88,10 @@ display: none; /* Chrome, Safari, Opera */ } + .squircle { + corner-shape: squircle; + } + /* Smooth playback scrubber */ input[type="range"] { -webkit-appearance: none; From 9998b43acc254feaaac53ae9e8495cf8f8f5ff0d Mon Sep 17 00:00:00 2001 From: Charles Ikechukwu <120549491+michTheBrandofficial@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:57:26 +0100 Subject: [PATCH 069/152] Update src/components/launch/SourceSelector.module.css Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/launch/SourceSelector.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/launch/SourceSelector.module.css b/src/components/launch/SourceSelector.module.css index 9d0d580..48d5507 100644 --- a/src/components/launch/SourceSelector.module.css +++ b/src/components/launch/SourceSelector.module.css @@ -8,9 +8,9 @@ Removed box-shadow here because electron doesn't round corners of the shadow, thereby leaving a square border shadow conflicting with the rounded corners of the SourceSelector. The result is easily visible when you place a white window just behind the SourceSelector */ - /*box-shadow: + /* box-shadow: 0 0px 16px 0 rgba(0, 0, 0, 0.32), - 0 1px 3px 0 rgba(0, 0, 0, 0.18) inset;*/ + 0 1px 3px 0 rgba(0, 0, 0, 0.18) inset; */ border: 1.5px solid rgba(60, 60, 80, 0.3); } From ee395b789650c08f2d62f387c9076a13371d22ed Mon Sep 17 00:00:00 2001 From: imAaryash Date: Wed, 15 Apr 2026 22:01:28 +0530 Subject: [PATCH 070/152] added discord.yaml --- .github/workflows/discord.yaml | 501 +++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 .github/workflows/discord.yaml diff --git a/.github/workflows/discord.yaml b/.github/workflows/discord.yaml new file mode 100644 index 0000000..3b07ad0 --- /dev/null +++ b/.github/workflows/discord.yaml @@ -0,0 +1,501 @@ +name: PR to Discord Forum + +on: + pull_request: + types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, edited, labeled, unlabeled, closed] + pull_request_review: + types: [submitted] + issue_comment: + types: [created] + schedule: + - cron: "0 12 * * 1" + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: read + +jobs: + notify: + if: github.event_name != 'schedule' + runs-on: ubuntu-latest + steps: + - name: Sync PR activity to Discord forum thread + id: sync + uses: actions/github-script@v7 + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_PR_FORUM_WEBHOOK }} + DISCORD_WEBHOOK_USERNAME: ${{ secrets.DISCORD_WEBHOOK_USERNAME }} + DISCORD_WEBHOOK_AVATAR_URL: ${{ secrets.DISCORD_WEBHOOK_AVATAR_URL }} + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + DISCORD_REVIEWER_ROLE_ID: ${{ secrets.DISCORD_REVIEWER_ROLE_ID }} + DISCORD_ALERT_WEBHOOK_URL: ${{ secrets.DISCORD_ALERT_WEBHOOK_URL }} + with: + script: | + const WEBHOOK_USERNAME = (process.env.DISCORD_WEBHOOK_USERNAME || "OpenScreen").trim(); + const WEBHOOK_AVATAR = (process.env.DISCORD_WEBHOOK_AVATAR_URL || "").trim(); + + const THREAD_MARKER_REGEX = //i; + const webhookUrl = (process.env.DISCORD_WEBHOOK_URL || "").trim(); + const botToken = (process.env.DISCORD_BOT_TOKEN || "").trim(); + const reviewerRoleId = (process.env.DISCORD_REVIEWER_ROLE_ID || "").trim(); + const alertWebhookUrl = (process.env.DISCORD_ALERT_WEBHOOK_URL || "").trim(); + + const TAGS = { + open: "1493976692967080096", + draft: "1493976782028935279", + ready: "1493976833626996756", + changes: "1493976909875515564", + approved: "1493976951038152764", + merged: "1493977049709281320", + closed: "1493977108102516786", + }; + + const labelTagMap = { + bug: "1493977562773458975", + enhancement: "1493977619216207993", + documentation: "1493978565153394830", + }; + + function cleanDescription(text, maxLen = 3500) { + if (!text) return "No description provided."; + const normalized = text + .replace(/\r\n/g, "\n") + .replace(/\n{3,}/g, "\n\n") + .trim(); + if (normalized.length <= maxLen) return normalized; + return `${normalized.slice(0, maxLen - 1)}…`; + } + + function trimThreadName(name) { + return name.length > 95 ? name.slice(0, 95) : name; + } + + function extractThreadId(body) { + if (!body) return null; + const match = body.match(THREAD_MARKER_REGEX); + return match ? match[1] : null; + } + + function upsertThreadMarker(body, threadId) { + const cleaned = (body || "").replace(THREAD_MARKER_REGEX, "").trim(); + return `${cleaned}\n\n`.trim(); + } + + async function discordPost(payload, options = {}) { + const endpoint = new URL(webhookUrl); + endpoint.searchParams.set("wait", "true"); + if (options.threadId) endpoint.searchParams.set("thread_id", String(options.threadId)); + + const response = await fetch(endpoint.toString(), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: WEBHOOK_USERNAME, + avatar_url: WEBHOOK_AVATAR, + allowed_mentions: { parse: [] }, + ...payload, + }) + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Discord API error ${response.status}: ${text}`); + } + + const text = await response.text(); + return text ? JSON.parse(text) : {}; + } + + async function patchDiscordThread(threadId, patchBody) { + if (!botToken || !threadId) return; + const response = await fetch(`https://discord.com/api/v10/channels/${threadId}`, { + method: "PATCH", + headers: { + "Authorization": `Bot ${botToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(patchBody), + }); + if (!response.ok) { + const text = await response.text(); + core.warning(`Discord thread patch failed (${response.status}): ${text}`); + } + } + + function desiredStatusTag(prState) { + if (prState.merged && TAGS.merged) return TAGS.merged; + if (prState.closed && !prState.merged && TAGS.closed) return TAGS.closed; + if (prState.reviewState === "CHANGES_REQUESTED" && TAGS.changes) return TAGS.changes; + if (prState.reviewState === "APPROVED" && TAGS.approved) return TAGS.approved; + if (prState.draft && TAGS.draft) return TAGS.draft; + if (!prState.draft && TAGS.ready) return TAGS.ready; + return TAGS.open || null; + } + + function tagIdsFromLabels(labels) { + const out = []; + for (const label of labels) { + const mapped = labelTagMap[label.toLowerCase()] || labelTagMap[label]; + if (mapped) out.push(String(mapped)); + } + return out; + } + + async function getPullRequest() { + if (context.eventName === "pull_request" || context.eventName === "pull_request_review") { + return context.payload.pull_request || null; + } + if (context.eventName === "issue_comment") { + const issue = context.payload.issue; + if (!issue?.pull_request) return null; + const { data } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue.number, + }); + return data; + } + return null; + } + + async function getReviewState(owner, repo, pullNumber) { + const { data } = await github.rest.pulls.listReviews({ owner, repo, pull_number: pullNumber, per_page: 100 }); + let hasChanges = false; + let hasApproved = false; + for (const r of data) { + const s = (r.state || "").toUpperCase(); + if (s === "CHANGES_REQUESTED") hasChanges = true; + if (s === "APPROVED") hasApproved = true; + } + if (hasChanges) return "CHANGES_REQUESTED"; + if (hasApproved) return "APPROVED"; + return "NONE"; + } + + async function sendFailureAlert(message) { + if (!alertWebhookUrl) return; + try { + await fetch(alertWebhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: "OpenScreen", + avatar_url: WEBHOOK_AVATAR, + content: `⚠️ PR Discord sync failed\n${message}\nRun: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + allowed_mentions: { parse: [] } + }) + }); + } catch { + core.warning("Failed to send failure alert webhook."); + } + } + + try { + if (!webhookUrl) { + core.setFailed("Missing webhook URL (DISCORD_PR_FORUM_WEBHOOK)."); + return; + } + + const pr = await getPullRequest(); + if (!pr) { + core.info("No PR context found. Skipping."); + return; + } + + const action = context.payload.action || ""; + const owner = context.repo.owner; + const repo = context.repo.repo; + const number = pr.number; + const title = pr.title; + const author = pr.user?.login || "unknown"; + const url = pr.html_url; + const authorUrl = pr.user?.html_url || ""; + const authorAvatar = pr.user?.avatar_url || ""; + const base = pr.base?.ref || ""; + const head = pr.head?.ref || ""; + const repoFullName = pr.base?.repo?.full_name || `${owner}/${repo}`; + const labels = (pr.labels || []).map((l) => l.name); + const body = (pr.body || "").trim(); + const reviewState = await getReviewState(owner, repo, number); + + let threadId = extractThreadId(body); + const shouldCreateThread = + context.eventName === "pull_request" && + ["opened", "reopened", "ready_for_review"].includes(action) && + !threadId; + + if (shouldCreateThread) { + const fields = [ + { name: "PR", value: `[#${number}](${url})`, inline: true }, + { name: "Author", value: `[${author}](${authorUrl || url})`, inline: true }, + { name: "Status", value: pr.draft ? "Draft" : "Open", inline: true }, + { name: "Branches", value: `\`${head}\` -> \`${base}\``, inline: true }, + { name: "Changes", value: `+${pr.additions} / -${pr.deletions}`, inline: true }, + { name: "Files Changed", value: String(pr.changed_files), inline: true } + ]; + + if (labels.length) { + fields.push({ + name: "Labels", + value: labels.map((l) => `\`${l}\``).join(" "), + inline: false, + }); + } + + const statusTag = desiredStatusTag({ draft: pr.draft, reviewState, merged: false, closed: false }); + const mappedLabelTags = tagIdsFromLabels(labels); + const appliedTags = [...new Set([statusTag, ...mappedLabelTags].filter(Boolean))]; + + const createPayload = { + content: action === "ready_for_review" ? "🔔 PR is now ready for review" : "🔔 New pull request opened", + thread_name: trimThreadName(`PR #${number} - ${title}`), + applied_tags: appliedTags, + embeds: [ + { + title: `PR #${number}: ${title}`, + url, + description: cleanDescription(body), + color: pr.draft ? 15105570 : 1998671, + author: { + name: author, + url: authorUrl || undefined, + icon_url: authorAvatar || undefined, + }, + fields, + footer: { text: repoFullName }, + timestamp: new Date().toISOString(), + }, + ], + }; + + const result = await discordPost(createPayload); + const createdThreadId = result.channel_id || null; + if (createdThreadId) { + const updatedBody = upsertThreadMarker(body, createdThreadId); + await github.rest.pulls.update({ owner, repo, pull_number: number, body: updatedBody }); + core.info(`Created Discord thread ${createdThreadId} and stored mapping.`); + } else { + core.warning("Discord thread created but channel_id missing in response."); + } + return; + } + + if (!threadId) { + core.info("No mapped Discord thread ID found; skipping update event."); + return; + } + + if (context.eventName === "pull_request" && ["edited", "labeled", "unlabeled", "ready_for_review", "converted_to_draft"].includes(action)) { + const statusTag = desiredStatusTag({ + draft: action === "converted_to_draft" ? true : pr.draft, + reviewState, + merged: false, + closed: false, + }); + const mappedLabelTags = tagIdsFromLabels(labels); + const appliedTags = [...new Set([statusTag, ...mappedLabelTags].filter(Boolean))]; + await patchDiscordThread(threadId, { + name: trimThreadName(`PR #${number} - ${title}`), + ...(appliedTags.length ? { applied_tags: appliedTags } : {}), + }); + } + + let updateMessage = null; + let updateEmbed = null; + + if (context.eventName === "pull_request") { + if (action === "synchronize") { + const { data: commits } = await github.rest.pulls.listCommits({ owner, repo, pull_number: number, per_page: 5 }); + const list = commits.map((c) => `- \`${c.sha.slice(0, 7)}\` ${c.commit.message.split("\n")[0]}`).join("\n") || "- No commit details"; + updateMessage = `🧩 New commits pushed to PR #${number}`; + updateEmbed = { + title: `Commit Update • PR #${number}`, + url: `${url}/files`, + description: `${list}`, + color: 1998671, + footer: { text: repoFullName }, + timestamp: new Date().toISOString(), + }; + } else if (action === "edited") { + updateMessage = `✏️ PR #${number} details were edited`; + updateEmbed = { + title: `PR Updated • #${number}`, + url, + description: cleanDescription(body, 1200), + color: 1998671, + timestamp: new Date().toISOString(), + }; + } else if (action === "closed") { + const isMerged = !!pr.merged; + const statusTag = desiredStatusTag({ draft: false, reviewState, merged: isMerged, closed: true }); + const mappedLabelTags = tagIdsFromLabels(labels); + const appliedTags = [...new Set([statusTag, ...mappedLabelTags].filter(Boolean))]; + await patchDiscordThread(threadId, { + ...(appliedTags.length ? { applied_tags: appliedTags } : {}), + ...(isMerged ? { archived: true, locked: true } : {}), + }); + + updateMessage = isMerged + ? `✅ PR #${number} was merged` + : `🛑 PR #${number} was closed without merge`; + updateEmbed = { + title: isMerged ? `Merged • PR #${number}` : `Closed • PR #${number}`, + url, + description: isMerged ? "This PR has been merged into the base branch." : "This PR was closed before merge.", + color: isMerged ? 5763719 : 15158332, + timestamp: new Date().toISOString(), + }; + } else if (action === "ready_for_review") { + updateMessage = `🚀 PR #${number} moved from draft to ready for review`; + if (reviewerRoleId) updateMessage += ` <@&${reviewerRoleId}>`; + } else if (action === "converted_to_draft") { + updateMessage = `📝 PR #${number} converted to draft`; + } + } else if (context.eventName === "pull_request_review") { + const review = context.payload.review; + if (review) { + const state = (review.state || "commented").toUpperCase(); + const reviewer = review.user?.login || "reviewer"; + updateMessage = `🧪 Review ${state} by **${reviewer}** on PR #${number}`; + if (state === "CHANGES_REQUESTED" && reviewerRoleId) updateMessage += ` <@&${reviewerRoleId}>`; + updateEmbed = { + title: `Review ${state} • PR #${number}`, + url: review.html_url || url, + description: cleanDescription(review.body || "No review note.", 1000), + color: state === "APPROVED" ? 5763719 : state === "CHANGES_REQUESTED" ? 15158332 : 1998671, + timestamp: new Date().toISOString(), + }; + + if (state === "CHANGES_REQUESTED" || state === "APPROVED") { + const statusTag = desiredStatusTag({ draft: pr.draft, reviewState: state, merged: false, closed: false }); + const mappedLabelTags = tagIdsFromLabels(labels); + const appliedTags = [...new Set([statusTag, ...mappedLabelTags].filter(Boolean))]; + await patchDiscordThread(threadId, { + ...(appliedTags.length ? { applied_tags: appliedTags } : {}), + }); + } + } + } else if (context.eventName === "issue_comment") { + const comment = context.payload.comment; + if (comment) { + const commenter = comment.user?.login || "user"; + updateMessage = `💬 New comment by **${commenter}** on PR #${number}`; + updateEmbed = { + title: `New PR Comment • #${number}`, + url: comment.html_url || url, + description: cleanDescription(comment.body || "No comment body.", 1000), + color: 1998671, + timestamp: new Date().toISOString(), + }; + } + } + + if (!updateMessage && !updateEmbed) { + core.info("No Discord update message for this event/action. Skipping."); + return; + } + + const payload = { content: updateMessage || "" }; + if (updateEmbed) payload.embeds = [updateEmbed]; + await discordPost(payload, { threadId }); + core.info(`Posted update to Discord thread ${threadId}.`); + } catch (err) { + const msg = err && err.message ? err.message : String(err); + core.setFailed(msg); + + const alertWebhook = process.env.DISCORD_ALERT_WEBHOOK_URL; + if (alertWebhook) { + try { + await fetch(alertWebhook, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: "OpenScreen", + avatar_url: WEBHOOK_AVATAR, + content: `⚠️ PR->Discord sync failed\n${msg}\nRun: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, + allowed_mentions: { parse: [] } + }) + }); + } catch { + core.warning("Failed to send alert webhook."); + } + } + } + + weekly-contributor-leaderboard: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Post weekly contributor leaderboard + uses: actions/github-script@v7 + env: + DISCORD_SPOTLIGHT_WEBHOOK_URL: ${{ secrets.DISCORD_SPOTLIGHT_WEBHOOK_URL }} + DISCORD_WEBHOOK_USERNAME: ${{ secrets.DISCORD_WEBHOOK_USERNAME }} + DISCORD_WEBHOOK_AVATAR_URL: ${{ secrets.DISCORD_WEBHOOK_AVATAR_URL }} + with: + script: | + const spotlightWebhook = (process.env.DISCORD_SPOTLIGHT_WEBHOOK_URL || "").trim(); + const webhookUsername = (process.env.DISCORD_WEBHOOK_USERNAME || "OpenScreen").trim(); + const webhookAvatar = (process.env.DISCORD_WEBHOOK_AVATAR_URL || "").trim(); + if (!spotlightWebhook) { + core.info("DISCORD_SPOTLIGHT_WEBHOOK_URL missing. Skipping leaderboard post."); + return; + } + + const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); + const owner = context.repo.owner; + const repo = context.repo.repo; + + const q = `repo:${owner}/${repo} is:pr is:merged merged:>=${since.substring(0, 10)}`; + const search = await github.rest.search.issuesAndPullRequests({ + q, + per_page: 100, + }); + + const counter = new Map(); + for (const item of search.data.items) { + const login = item.user?.login; + if (!login) continue; + counter.set(login, (counter.get(login) || 0) + 1); + } + + const ranked = [...counter.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(0, 10); + + const totalMerged = search.data.items.length; + const lines = ranked.length + ? ranked.map(([user, count], idx) => `${idx + 1}. **${user}** - ${count} merged PR(s)`).join("\n") + : "No merged PRs this week."; + + const payload = { + username: webhookUsername, + ...(webhookAvatar ? { avatar_url: webhookAvatar } : {}), + embeds: [ + { + title: "🌟 Weekly Contributor Leaderboard", + description: lines, + color: 1998671, + fields: [ + { name: "Merged PRs (7d)", value: String(totalMerged), inline: true }, + { name: "Repository", value: `${owner}/${repo}`, inline: true }, + { name: "Period", value: "Last 7 days", inline: true } + ], + timestamp: new Date().toISOString() + } + ], + allowed_mentions: { parse: [] } + }; + + const res = await fetch(`${spotlightWebhook}?wait=true`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + + if (!res.ok) { + const txt = await res.text(); + core.setFailed(`Leaderboard post failed ${res.status}: ${txt}`); + } From 84ec5a7e68cfdd3fe5625bdaa9c3d8b2eb96c118 Mon Sep 17 00:00:00 2001 From: shaun0927 Date: Thu, 16 Apr 2026 10:27:20 +0900 Subject: [PATCH 071/152] fix: isolate cursor telemetry samples per recording session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the main process kept two module-scope arrays — activeCursorSamples and pendingCursorSamples — and set-recording-state on a new recording wiped BOTH. When a user stopped recording and immediately started a new one before store-recorded-session fired, the previous recording's pending samples were discarded or later overwritten with the new session's data, producing empty or mismatched .cursor.json files. Replace the two arrays with a small FIFO buffer (createCursorTelemetryBuffer) that: - Keeps pending batches per completed recording, never wiping them on a new session start. - Yields batches in arrival order to storeRecordedSessionFiles. - Caps pending batches (default 8) so a never-stored sequence cannot leak unbounded memory. Unit-tested directly in src/lib/cursorTelemetryBuffer.test.ts, including the rapid-restart race that motivated the change. --- electron/ipc/handlers.ts | 33 +++----- src/lib/cursorTelemetryBuffer.test.ts | 113 ++++++++++++++++++++++++++ src/lib/cursorTelemetryBuffer.ts | 66 +++++++++++++++ 3 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 src/lib/cursorTelemetryBuffer.test.ts create mode 100644 src/lib/cursorTelemetryBuffer.ts diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 4cb4875..284a671 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -11,6 +11,10 @@ import { shell, systemPreferences, } from "electron"; +import { + type CursorTelemetryPoint, + createCursorTelemetryBuffer, +} from "../../src/lib/cursorTelemetryBuffer"; import { normalizeProjectMedia, normalizeRecordingSession, @@ -275,14 +279,14 @@ async function storeRecordedSessionFiles(payload: StoreRecordedSessionInput) { currentProjectPath = null; const telemetryPath = `${screenVideoPath}.cursor.json`; - if (pendingCursorSamples.length > 0) { + const pendingSamples: CursorTelemetryPoint[] = cursorTelemetryBuffer.takeNextBatch(); + if (pendingSamples.length > 0) { await fs.writeFile( telemetryPath, - JSON.stringify({ version: CURSOR_TELEMETRY_VERSION, samples: pendingCursorSamples }, null, 2), + JSON.stringify({ version: CURSOR_TELEMETRY_VERSION, samples: pendingSamples }, null, 2), "utf-8", ); } - pendingCursorSamples = []; const sessionManifestPath = path.join( RECORDINGS_DIR, @@ -302,16 +306,11 @@ const CURSOR_TELEMETRY_VERSION = 1; const CURSOR_SAMPLE_INTERVAL_MS = 100; const MAX_CURSOR_SAMPLES = 60 * 60 * 10; // 1 hour @ 10Hz -interface CursorTelemetryPoint { - timeMs: number; - cx: number; - cy: number; -} - let cursorCaptureInterval: NodeJS.Timeout | null = null; let cursorCaptureStartTimeMs = 0; -let activeCursorSamples: CursorTelemetryPoint[] = []; -let pendingCursorSamples: CursorTelemetryPoint[] = []; +const cursorTelemetryBuffer = createCursorTelemetryBuffer({ + maxActiveSamples: MAX_CURSOR_SAMPLES, +}); function clamp(value: number, min: number, max: number) { return Math.min(max, Math.max(min, value)); @@ -338,15 +337,11 @@ function sampleCursorPoint() { const cx = clamp((cursor.x - bounds.x) / width, 0, 1); const cy = clamp((cursor.y - bounds.y) / height, 0, 1); - activeCursorSamples.push({ + cursorTelemetryBuffer.push({ timeMs: Math.max(0, Date.now() - cursorCaptureStartTimeMs), cx, cy, }); - - if (activeCursorSamples.length > MAX_CURSOR_SAMPLES) { - activeCursorSamples.shift(); - } } export function registerIpcHandlers( @@ -534,15 +529,13 @@ export function registerIpcHandlers( ipcMain.handle("set-recording-state", (_, recording: boolean) => { if (recording) { stopCursorCapture(); - activeCursorSamples = []; - pendingCursorSamples = []; + cursorTelemetryBuffer.startSession(); cursorCaptureStartTimeMs = Date.now(); sampleCursorPoint(); cursorCaptureInterval = setInterval(sampleCursorPoint, CURSOR_SAMPLE_INTERVAL_MS); } else { stopCursorCapture(); - pendingCursorSamples = [...activeCursorSamples]; - activeCursorSamples = []; + cursorTelemetryBuffer.endSession(); } const source = selectedSource || { name: "Screen" }; diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts new file mode 100644 index 0000000..a626394 --- /dev/null +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, it } from "vitest"; +import { type CursorTelemetryPoint, createCursorTelemetryBuffer } from "./cursorTelemetryBuffer"; + +function sample(tag: number): CursorTelemetryPoint { + return { timeMs: tag, cx: tag / 10, cy: tag / 10 }; +} + +describe("createCursorTelemetryBuffer", () => { + it("stores samples captured during an active session", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + buf.startSession(); + for (let i = 0; i < 3; i++) buf.push(sample(i)); + buf.endSession(); + + const batch = buf.takeNextBatch(); + expect(batch).toHaveLength(3); + expect(batch[0]?.timeMs).toBe(0); + }); + + it("trims active samples past maxActiveSamples (ring behaviour)", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 2 }); + buf.startSession(); + buf.push(sample(1)); + buf.push(sample(2)); + buf.push(sample(3)); + buf.endSession(); + + const batch = buf.takeNextBatch(); + expect(batch).toEqual([sample(2), sample(3)]); + }); + + it("preserves earlier pending batches when a new session starts before store", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + + // Recording 1 + buf.startSession(); + buf.push(sample(101)); + buf.push(sample(102)); + buf.endSession(); + + // Recording 2 starts before recording 1's batch has been consumed + buf.startSession(); + buf.push(sample(201)); + buf.endSession(); + + const batch1 = buf.takeNextBatch(); + const batch2 = buf.takeNextBatch(); + expect(batch1.map((s) => s.timeMs)).toEqual([101, 102]); + expect(batch2.map((s) => s.timeMs)).toEqual([201]); + }); + + it("returns an empty batch when nothing is pending", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + expect(buf.takeNextBatch()).toEqual([]); + }); + + it("drops empty sessions instead of queuing empty batches", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + buf.startSession(); + buf.endSession(); + expect(buf.pendingCount).toBe(0); + expect(buf.takeNextBatch()).toEqual([]); + }); + + it("caps the pending queue at maxPendingBatches to bound memory", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10, maxPendingBatches: 3 }); + + for (let round = 1; round <= 5; round++) { + buf.startSession(); + buf.push(sample(round)); + buf.endSession(); + } + + expect(buf.pendingCount).toBe(3); + // Oldest two batches (rounds 1 and 2) should have been dropped + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([3]); + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([4]); + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([5]); + }); + + it("starting a new session clears in-progress samples but keeps pending batches", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + + buf.startSession(); + buf.push(sample(1)); + buf.endSession(); + + buf.startSession(); + buf.push(sample(99)); + // Simulate another startSession before endSession (e.g. rapid restart) + buf.startSession(); + expect(buf.activeCount).toBe(0); + expect(buf.pendingCount).toBe(1); + + const batch = buf.takeNextBatch(); + expect(batch.map((s) => s.timeMs)).toEqual([1]); + }); + + it("reset() clears both active and pending state", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + buf.startSession(); + buf.push(sample(1)); + buf.endSession(); + buf.startSession(); + buf.push(sample(2)); + + buf.reset(); + + expect(buf.activeCount).toBe(0); + expect(buf.pendingCount).toBe(0); + expect(buf.takeNextBatch()).toEqual([]); + }); +}); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts new file mode 100644 index 0000000..2b4ef0c --- /dev/null +++ b/src/lib/cursorTelemetryBuffer.ts @@ -0,0 +1,66 @@ +export interface CursorTelemetryPoint { + timeMs: number; + cx: number; + cy: number; +} + +export interface CursorTelemetryBuffer { + startSession(): void; + push(point: CursorTelemetryPoint): void; + endSession(): void; + takeNextBatch(): CursorTelemetryPoint[]; + reset(): void; + readonly activeCount: number; + readonly pendingCount: number; +} + +export interface CursorTelemetryBufferOptions { + maxActiveSamples: number; + maxPendingBatches?: number; +} + +const DEFAULT_MAX_PENDING_BATCHES = 8; + +export function createCursorTelemetryBuffer( + options: CursorTelemetryBufferOptions, +): CursorTelemetryBuffer { + const maxActive = options.maxActiveSamples; + const maxPending = options.maxPendingBatches ?? DEFAULT_MAX_PENDING_BATCHES; + + let active: CursorTelemetryPoint[] = []; + let pending: CursorTelemetryPoint[][] = []; + + return { + startSession() { + active = []; + }, + push(point) { + active.push(point); + if (active.length > maxActive) { + active.shift(); + } + }, + endSession() { + if (active.length > 0) { + pending.push(active); + while (pending.length > maxPending) { + pending.shift(); + } + } + active = []; + }, + takeNextBatch() { + return pending.shift() ?? []; + }, + reset() { + active = []; + pending = []; + }, + get activeCount() { + return active.length; + }, + get pendingCount() { + return pending.length; + }, + }; +} From 12f3be02f2902a80be908e4dd3d797900f10df92 Mon Sep 17 00:00:00 2001 From: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> Date: Thu, 16 Apr 2026 09:31:37 +0800 Subject: [PATCH 072/152] fix: sort lucide-react imports alphabetically Signed-off-by: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> --- src/components/video-editor/AnnotationSettingsPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index db3197f..2e25830 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,9 +1,9 @@ import Block from "@uiw/react-color-block"; import { AlignCenter, - Copy, AlignLeft, AlignRight, + Copy, Bold, ChevronDown, Image as ImageIcon, From fac0b405d30ea2b9b6bd76920a03dfbd96d4951c Mon Sep 17 00:00:00 2001 From: JunghwanNA <70629228+shaun0927@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:58:16 +0900 Subject: [PATCH 073/152] fix: handle recording discard and write-failure in cursor telemetry buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address two issues raised during review: P1 – When a recording is cancelled or restarted, setRecordingState(false) enqueues its cursor batch but store-recorded-session is never called, leaving a stale batch that contaminates the next recording's telemetry. Add discardLatestPending() to the buffer and a discard-cursor-telemetry IPC handler; the renderer now calls it on the discard path. P2 – takeNextBatch() dequeued the batch before fs.writeFile, so a write failure would permanently lose the telemetry. Wrap the write in try/catch and re-insert the batch via prependBatch() on failure. Co-Authored-By: Claude Opus 4.6 (1M context) --- electron/electron-env.d.ts | 1 + electron/ipc/handlers.ts | 19 +++++++++--- electron/preload.ts | 3 ++ src/hooks/useScreenRecorder.ts | 1 + src/lib/cursorTelemetryBuffer.test.ts | 44 +++++++++++++++++++++++++++ src/lib/cursorTelemetryBuffer.ts | 10 ++++++ 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index b2a3720..ea364a1 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -64,6 +64,7 @@ interface Window { error?: string; }>; setRecordingState: (recording: boolean) => Promise; + discardCursorTelemetry: () => Promise; getCursorTelemetry: (videoPath?: string) => Promise<{ success: boolean; samples: CursorTelemetryPoint[]; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 284a671..fc55006 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -281,11 +281,16 @@ async function storeRecordedSessionFiles(payload: StoreRecordedSessionInput) { const telemetryPath = `${screenVideoPath}.cursor.json`; const pendingSamples: CursorTelemetryPoint[] = cursorTelemetryBuffer.takeNextBatch(); if (pendingSamples.length > 0) { - await fs.writeFile( - telemetryPath, - JSON.stringify({ version: CURSOR_TELEMETRY_VERSION, samples: pendingSamples }, null, 2), - "utf-8", - ); + try { + await fs.writeFile( + telemetryPath, + JSON.stringify({ version: CURSOR_TELEMETRY_VERSION, samples: pendingSamples }, null, 2), + "utf-8", + ); + } catch (err) { + cursorTelemetryBuffer.prependBatch(pendingSamples); + throw err; + } } const sessionManifestPath = path.join( @@ -544,6 +549,10 @@ export function registerIpcHandlers( } }); + ipcMain.handle("discard-cursor-telemetry", () => { + cursorTelemetryBuffer.discardLatestPending(); + }); + ipcMain.handle("get-cursor-telemetry", async (_, videoPath?: string) => { const targetVideoPath = normalizeVideoSourcePath( videoPath ?? currentRecordingSession?.screenVideoPath, diff --git a/electron/preload.ts b/electron/preload.ts index eeca25c..9367122 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -53,6 +53,9 @@ contextBridge.exposeInMainWorld("electronAPI", { getCursorTelemetry: (videoPath?: string) => { return ipcRenderer.invoke("get-cursor-telemetry", videoPath); }, + discardCursorTelemetry: () => { + return ipcRenderer.invoke("discard-cursor-telemetry"); + }, onStopRecordingFromTray: (callback: () => void) => { const listener = () => callback(); ipcRenderer.on("stop-recording-from-tray", listener); diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 5cbc54a..fd8a307 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -225,6 +225,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { try { const screenBlob = await activeScreenRecorder.recordedBlobPromise; if (discardRecordingId.current === activeRecordingId) { + window.electronAPI?.discardCursorTelemetry(); return; } if (screenBlob.size === 0) { diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts index a626394..5ffbc7a 100644 --- a/src/lib/cursorTelemetryBuffer.test.ts +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -96,6 +96,50 @@ describe("createCursorTelemetryBuffer", () => { expect(batch.map((s) => s.timeMs)).toEqual([1]); }); + it("discardLatestPending() drops the most recently enqueued batch", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + + buf.startSession(); + buf.push(sample(1)); + buf.endSession(); + + buf.startSession(); + buf.push(sample(2)); + buf.endSession(); + + expect(buf.pendingCount).toBe(2); + buf.discardLatestPending(); + expect(buf.pendingCount).toBe(1); + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + }); + + it("discardLatestPending() is safe to call on an empty queue", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + buf.discardLatestPending(); + expect(buf.pendingCount).toBe(0); + }); + + it("prependBatch() re-inserts a batch at the front of the queue", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + + buf.startSession(); + buf.push(sample(1)); + buf.endSession(); + + const batch = buf.takeNextBatch(); + expect(buf.pendingCount).toBe(0); + + buf.prependBatch(batch); + expect(buf.pendingCount).toBe(1); + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + }); + + it("prependBatch() ignores empty batches", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + buf.prependBatch([]); + expect(buf.pendingCount).toBe(0); + }); + it("reset() clears both active and pending state", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); buf.startSession(); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts index 2b4ef0c..d812610 100644 --- a/src/lib/cursorTelemetryBuffer.ts +++ b/src/lib/cursorTelemetryBuffer.ts @@ -9,6 +9,8 @@ export interface CursorTelemetryBuffer { push(point: CursorTelemetryPoint): void; endSession(): void; takeNextBatch(): CursorTelemetryPoint[]; + prependBatch(batch: CursorTelemetryPoint[]): void; + discardLatestPending(): void; reset(): void; readonly activeCount: number; readonly pendingCount: number; @@ -52,6 +54,14 @@ export function createCursorTelemetryBuffer( takeNextBatch() { return pending.shift() ?? []; }, + prependBatch(batch) { + if (batch.length > 0) { + pending.unshift(batch); + } + }, + discardLatestPending() { + pending.pop(); + }, reset() { active = []; pending = []; From 17bed0956d06c9938916e312b6940da6a5502d79 Mon Sep 17 00:00:00 2001 From: themaker Date: Thu, 16 Apr 2026 09:40:31 +0100 Subject: [PATCH 074/152] fix(package-lock.json): update package-lock.json to resolve dependencies mismatch --- package-lock.json | 1248 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1248 insertions(+) diff --git a/package-lock.json b/package-lock.json index 998f8f3..ba40beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4993,6 +4993,474 @@ } } }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", @@ -5019,6 +5487,630 @@ } } }, + "node_modules/@vitest/browser-playwright/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@vitest/browser-playwright/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/browser/node_modules/@vitest/mocker": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", @@ -5045,6 +6137,85 @@ } } }, + "node_modules/@vitest/browser/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/@vitest/browser/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@vitest/browser/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@vitest/browser/node_modules/pixelmatch": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", @@ -5066,6 +6237,83 @@ "node": ">=14.19.0" } }, + "node_modules/@vitest/browser/node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", From 8b7047365c80d7394ad10964773390ee1a9c5e9e Mon Sep 17 00:00:00 2001 From: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:00:48 +0800 Subject: [PATCH 075/152] style: sort lucide-react imports alphabetically to fix Biome lint --- src/components/video-editor/AnnotationSettingsPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index 2e25830..0b0c174 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -3,9 +3,9 @@ import { AlignCenter, AlignLeft, AlignRight, - Copy, Bold, ChevronDown, + Copy, Image as ImageIcon, Info, Italic, From 64e011f79889835e703cccdf1f6cf2155975eab0 Mon Sep 17 00:00:00 2001 From: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:01:02 +0800 Subject: [PATCH 076/152] style: wrap long onDuplicate prop to fix Biome formatter --- src/components/video-editor/SettingsPanel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 96a61af..d538666 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -468,7 +468,9 @@ export function SettingsPanel({ ? (figureData) => onAnnotationFigureDataChange(selectedAnnotation.id, figureData) : undefined } - onDuplicate={onAnnotationDuplicate ? () => onAnnotationDuplicate(selectedAnnotation.id) : undefined} + onDuplicate={ + onAnnotationDuplicate ? () => onAnnotationDuplicate(selectedAnnotation.id) : undefined + } onDelete={() => onAnnotationDelete(selectedAnnotation.id)} /> ); From 501c4f20a1fbfae98f738f096bd929607d49ec30 Mon Sep 17 00:00:00 2001 From: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:29:05 +0800 Subject: [PATCH 077/152] fix: remove unused COMPARE_LOCALES variable in i18n-check.mjs to pass Biome lint --- scripts/i18n-check.mjs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index 3fd0331..699ae9e 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** * Validates that all locale translation files have identical key structures. - * Compares zh-CN and es against the en baseline for every namespace. + * Compares all locale folders (except en) against the en baseline for every namespace. * * Usage: node scripts/i18n-check.mjs */ @@ -11,7 +11,6 @@ import path from "node:path"; const LOCALES_DIR = path.resolve("src/i18n/locales"); const BASE_LOCALE = "en"; -const COMPARE_LOCALES = ["zh-CN", "es"]; function getKeys(obj, prefix = "") { const keys = []; @@ -34,12 +33,19 @@ const namespaces = fs .filter((f) => f.endsWith(".json")) .map((f) => f.replace(".json", "")); +const compareLocales = fs + .readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((locale) => locale !== BASE_LOCALE) + .sort((a, b) => a.localeCompare(b)); + for (const namespace of namespaces) { const basePath = path.join(baseDir, `${namespace}.json`); const baseData = JSON.parse(fs.readFileSync(basePath, "utf-8")); const baseKeys = getKeys(baseData); - for (const locale of COMPARE_LOCALES) { + for (const locale of compareLocales) { const localePath = path.join(LOCALES_DIR, locale, `${namespace}.json`); if (!fs.existsSync(localePath)) { @@ -77,6 +83,6 @@ if (hasErrors) { process.exit(1); } else { console.log( - `i18n check PASSED — all ${COMPARE_LOCALES.length} locales match ${BASE_LOCALE} across ${namespaces.length} namespaces.`, + `i18n check PASSED — all ${compareLocales.length} locales match ${BASE_LOCALE} across ${namespaces.length} namespaces.`, ); } From 7264b9989ee71e7093f411b366276007d0b18557 Mon Sep 17 00:00:00 2001 From: Aaryash Khalkar <91302334+imAaryash@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:16:56 +0530 Subject: [PATCH 078/152] Refactor Discord webhook URL handling in workflow Updated Discord webhook handling to allow for a fallback to DISCORD_PR_FORUM_WEBHOOK if DISCORD_WEBHOOK_URL is not set. Added checks to ensure webhook URL is provided, especially for fork PR events. --- .github/workflows/discord.yaml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/discord.yaml b/.github/workflows/discord.yaml index 3b07ad0..97d23e7 100644 --- a/.github/workflows/discord.yaml +++ b/.github/workflows/discord.yaml @@ -25,7 +25,8 @@ jobs: id: sync uses: actions/github-script@v7 env: - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_PR_FORUM_WEBHOOK }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + DISCORD_PR_FORUM_WEBHOOK: ${{ secrets.DISCORD_PR_FORUM_WEBHOOK }} DISCORD_WEBHOOK_USERNAME: ${{ secrets.DISCORD_WEBHOOK_USERNAME }} DISCORD_WEBHOOK_AVATAR_URL: ${{ secrets.DISCORD_WEBHOOK_AVATAR_URL }} DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} @@ -37,7 +38,7 @@ jobs: const WEBHOOK_AVATAR = (process.env.DISCORD_WEBHOOK_AVATAR_URL || "").trim(); const THREAD_MARKER_REGEX = //i; - const webhookUrl = (process.env.DISCORD_WEBHOOK_URL || "").trim(); + const webhookUrl = (process.env.DISCORD_WEBHOOK_URL || process.env.DISCORD_PR_FORUM_WEBHOOK || "").trim(); const botToken = (process.env.DISCORD_BOT_TOKEN || "").trim(); const reviewerRoleId = (process.env.DISCORD_REVIEWER_ROLE_ID || "").trim(); const alertWebhookUrl = (process.env.DISCORD_ALERT_WEBHOOK_URL || "").trim(); @@ -193,17 +194,24 @@ jobs: } try { - if (!webhookUrl) { - core.setFailed("Missing webhook URL (DISCORD_PR_FORUM_WEBHOOK)."); - return; - } - const pr = await getPullRequest(); if (!pr) { core.info("No PR context found. Skipping."); return; } + const isForkPr = !!pr.head?.repo?.fork; + if (!webhookUrl) { + if (isForkPr) { + core.info("Skipping Discord sync: webhook secret is unavailable for fork PR events."); + return; + } + core.setFailed( + "Missing Discord webhook secret. Set either DISCORD_WEBHOOK_URL or DISCORD_PR_FORUM_WEBHOOK in repository secrets, or pass it explicitly if using reusable workflows." + ); + return; + } + const action = context.payload.action || ""; const owner = context.repo.owner; const repo = context.repo.repo; From 5e62ad3215b0c9b24ab3aaa0568675b35e4fab26 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 17:54:43 -0500 Subject: [PATCH 079/152] fix: validate export duration and fix audio trim in speed-aware path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in the export pipeline: 1. Container duration from WebM metadata can be unreliable (Chromium bug on Linux — reports Infinity, 0, or inflated values). The pipeline trusted this value, causing inflated exports, frozen video, and "decode ended early" errors. Fix: scan actual packet timestamps in loadMetadata() and compare against container duration. Use packet-based ground truth when they diverge. 2. The speed-aware audio path (renderPitchPreservedTimelineAudio) recorded in real-time via MediaRecorder but never paused recording during trim-region seeks. Seek dead time was captured as audio, inflating the audio track beyond the video duration. Fix: pause MediaRecorder during trim seeks, skip past initial trim before recording starts, wait for seek completion before resuming. Fixes #276, #433. Partially addresses #428. --- src/lib/exporter/audioEncoder.ts | 106 +++++++++++++++++++--- src/lib/exporter/streamingDecoder.test.ts | 33 ++++++- src/lib/exporter/streamingDecoder.ts | 54 ++++++++++- src/lib/exporter/videoExporter.ts | 10 +- 4 files changed, 180 insertions(+), 23 deletions(-) diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index 490eed2..e373702 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -5,6 +5,7 @@ import type { VideoMuxer } from "./muxer"; const AUDIO_BITRATE = 128_000; const DECODE_BACKPRESSURE_LIMIT = 20; const MIN_SPEED_REGION_DELTA_MS = 0.0001; +const SEEK_TIMEOUT_MS = 5_000; export class AudioProcessor { private cancelled = false; @@ -20,7 +21,7 @@ export class AudioProcessor { videoUrl: string, trimRegions?: TrimRegion[], speedRegions?: SpeedRegion[], - readEndSec?: number, + validatedDurationSec?: number, ): Promise { const sortedTrims = trimRegions ? [...trimRegions].sort((a, b) => a.startMs - b.startMs) : []; const sortedSpeedRegions = speedRegions @@ -35,14 +36,20 @@ export class AudioProcessor { videoUrl, sortedTrims, sortedSpeedRegions, + validatedDurationSec, ); - if (!this.cancelled) { + if (!this.cancelled && renderedAudioBlob.size > 0) { await this.muxRenderedAudioBlob(renderedAudioBlob, muxer); return; } + return; } // No speed edits: keep the original demux/decode/encode path with trim timestamp remap. + const readEndSec = + typeof validatedDurationSec === "number" && Number.isFinite(validatedDurationSec) + ? validatedDurationSec + 0.5 + : undefined; await this.processTrimOnlyAudio(demuxer, muxer, sortedTrims, readEndSec); } @@ -187,6 +194,7 @@ export class AudioProcessor { videoUrl: string, trimRegions: TrimRegion[], speedRegions: SpeedRegion[], + validatedDurationSec?: number, ): Promise { const media = document.createElement("audio"); media.src = videoUrl; @@ -211,15 +219,41 @@ export class AudioProcessor { const destinationNode = audioContext.createMediaStreamDestination(); sourceNode.connect(destinationNode); - const { recorder, recordedBlobPromise } = this.startAudioRecording(destinationNode.stream); let rafId: number | null = null; + let recorder: MediaRecorder | null = null; + let recordedBlobPromise: Promise | null = null; try { if (audioContext.state === "suspended") { await audioContext.resume(); } - await this.seekTo(media, 0); + // Skip past any initial trim region before recording starts + // to avoid capturing trimmed audio during the first frames. + let startPosition = 0; + const initialTrim = this.findActiveTrimRegion(0, trimRegions); + if (initialTrim) { + startPosition = initialTrim.endMs / 1000; + } + + const effectiveEnd = validatedDurationSec ?? media.duration; + if (startPosition >= effectiveEnd) { + // All content is trimmed — return silent blob + return new Blob([], { type: "audio/webm" }); + } + + await this.seekTo(media, startPosition); + + // Set initial playback rate for the starting position + const initialSpeedRegion = this.findActiveSpeedRegion(startPosition * 1000, speedRegions); + if (initialSpeedRegion) { + media.playbackRate = initialSpeedRegion.speed; + } + + // Start recording only AFTER seeking past trims + const recording = this.startAudioRecording(destinationNode.stream); + recorder = recording.recorder; + recordedBlobPromise = recording.recordedBlobPromise; await media.play(); await new Promise((resolve, reject) => { @@ -249,24 +283,69 @@ export class AudioProcessor { return; } + // Stop playback at validated duration — browser's media.duration + // may be inflated from bad container metadata. + if (validatedDurationSec !== undefined && media.currentTime >= validatedDurationSec) { + media.pause(); + cleanup(); + resolve(); + return; + } + const currentTimeMs = media.currentTime * 1000; const activeTrimRegion = this.findActiveTrimRegion(currentTimeMs, trimRegions); if (activeTrimRegion && !media.paused && !media.ended) { const skipToTime = activeTrimRegion.endMs / 1000; - if (skipToTime >= media.duration) { + if ( + skipToTime >= media.duration || + (validatedDurationSec !== undefined && skipToTime >= validatedDurationSec) + ) { media.pause(); cleanup(); resolve(); return; } + // Pause recording during trim seek to prevent capturing + // silence/noise as the audio element seeks. + media.pause(); + if (recorder?.state === "recording") recorder.pause(); + const onSeeked = () => { + clearTimeout(seekTimer); + if (this.cancelled) { + cleanup(); + resolve(); + return; + } + if (recorder?.state === "paused") recorder.resume(); + media + .play() + .then(() => { + if (!this.cancelled) rafId = requestAnimationFrame(tick); + }) + .catch((err) => { + cleanup(); + reject( + new Error( + `Failed to resume playback after trim seek: ${err instanceof Error ? err.message : String(err)}`, + ), + ); + }); + }; + const seekTimer = window.setTimeout(() => { + media.removeEventListener("seeked", onSeeked); + cleanup(); + reject(new Error("Audio seek timed out while skipping trim region")); + }, SEEK_TIMEOUT_MS); + media.addEventListener("seeked", onSeeked, { once: true }); media.currentTime = skipToTime; - } else { - const activeSpeedRegion = this.findActiveSpeedRegion(currentTimeMs, speedRegions); - const playbackRate = activeSpeedRegion ? activeSpeedRegion.speed : 1; - if (Math.abs(media.playbackRate - playbackRate) > 0.0001) { - media.playbackRate = playbackRate; - } + return; + } + + const activeSpeedRegion = this.findActiveSpeedRegion(currentTimeMs, speedRegions); + const playbackRate = activeSpeedRegion ? activeSpeedRegion.speed : 1; + if (Math.abs(media.playbackRate - playbackRate) > 0.0001) { + media.playbackRate = playbackRate; } if (!media.paused && !media.ended) { @@ -286,7 +365,7 @@ export class AudioProcessor { cancelAnimationFrame(rafId); } media.pause(); - if (recorder.state !== "inactive") { + if (recorder && recorder.state !== "inactive") { recorder.stop(); } destinationNode.stream.getTracks().forEach((track) => track.stop()); @@ -297,6 +376,9 @@ export class AudioProcessor { media.load(); } + if (!recordedBlobPromise) { + return new Blob([], { type: "audio/webm" }); + } const recordedBlob = await recordedBlobPromise; if (this.cancelled) { throw new Error("Export cancelled"); diff --git a/src/lib/exporter/streamingDecoder.test.ts b/src/lib/exporter/streamingDecoder.test.ts index 1969c84..e3a0ecc 100644 --- a/src/lib/exporter/streamingDecoder.test.ts +++ b/src/lib/exporter/streamingDecoder.test.ts @@ -1,5 +1,36 @@ import { describe, expect, it } from "vitest"; -import { shouldFailDecodeEndedEarly } from "./streamingDecoder"; +import { shouldFailDecodeEndedEarly, validateDuration } from "./streamingDecoder"; + +describe("validateDuration", () => { + it("returns scanned duration when container reports Infinity", () => { + expect(validateDuration(Infinity, 15.3)).toBe(15.3); + }); + + it("returns scanned duration when container reports 0", () => { + expect(validateDuration(0, 15.3)).toBe(15.3); + }); + + it("returns scanned duration when container reports NaN", () => { + expect(validateDuration(NaN, 15.3)).toBe(15.3); + }); + + it("returns scanned duration when container is inflated beyond threshold", () => { + expect(validateDuration(42, 15.3)).toBe(15.3); + }); + + it("returns container duration when values are close", () => { + expect(validateDuration(15.5, 15.3)).toBe(15.5); + }); + + it("returns container duration when scanned is slightly higher", () => { + // container < scanned (scanned overshoot from last frame duration) + expect(validateDuration(15.0, 15.3)).toBe(15.0); + }); + + it("returns container duration when scanned is zero (corrupted/empty file)", () => { + expect(validateDuration(10, 0)).toBe(10); + }); +}); describe("shouldFailDecodeEndedEarly", () => { it("does not fail once every segment has been satisfied", () => { diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 651a557..7e261ff 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -70,6 +70,32 @@ type EarlyDecodeEndCheck = { const EARLY_DECODE_END_THRESHOLD_SEC = 1; const METADATA_TAIL_TOLERANCE_SEC = 1.5; const STREAM_DURATION_MATCH_TOLERANCE_SEC = 0.25; +const DURATION_DIVERGENCE_THRESHOLD_SEC = 1.5; + +/** + * Validate container duration against actual packet timestamps. + * + * Chrome/Electron's MediaRecorder writes WebM containers with unreliable + * Duration fields (often Infinity, 0, or inflated) — especially on Linux. + * This function picks the most trustworthy duration value. + * + * @param containerDuration Duration from the container-level metadata + * @param scannedDuration Duration derived from actual packet timestamps (ground truth) + */ +export function validateDuration(containerDuration: number, scannedDuration: number): number { + if (scannedDuration <= 0) { + // Zero scanned duration means corrupted/empty file — fall back to container + // (downstream shouldFailDecodeEndedEarly will catch truly empty files) + return Math.max(containerDuration, 0); + } + if (!Number.isFinite(containerDuration) || containerDuration <= 0) { + return scannedDuration; + } + if (containerDuration - scannedDuration > DURATION_DIVERGENCE_THRESHOLD_SEC) { + return scannedDuration; + } + return containerDuration; +} export function shouldFailDecodeEndedEarly({ cancelled, @@ -201,10 +227,34 @@ export class StreamingVideoDecoder { const audioStream = mediaInfo.streams.find((s) => s.codec_type_string === "audio"); + // Scan video packets to find the true content boundary. + // MediaRecorder (especially on Linux) writes unreliable container durations. + // Packet timestamps are ground truth — no decode needed, just timestamp reads. + let maxPacketEndUs = 0; + const scanReader = ( + this.demuxer.read("video") as ReadableStream + ).getReader(); + try { + while (true) { + const { done, value } = await scanReader.read(); + if (done || !value) break; + const endUs = value.timestamp + (value.duration ?? 0); + if (endUs > maxPacketEndUs) maxPacketEndUs = endUs; + } + } finally { + try { + await scanReader.cancel(); + } catch { + /* already closed */ + } + } + const scannedDuration = maxPacketEndUs / 1_000_000; + const validatedDuration = validateDuration(mediaInfo.duration, scannedDuration); + this.metadata = { width: videoStream?.width || 1920, height: videoStream?.height || 1080, - duration: mediaInfo.duration, + duration: validatedDuration, streamDuration: typeof videoStream?.duration === "number" && Number.isFinite(videoStream.duration) ? videoStream.duration @@ -305,7 +355,7 @@ export class StreamingVideoDecoder { // One forward stream through the whole file. // Pass explicit range because some containers are truncated when no end is provided. - const readEndSec = Math.max(this.metadata.duration, this.metadata.streamDuration ?? 0) + 0.5; + const readEndSec = this.metadata.duration + 0.5; const reader = this.demuxer.read("video", 0, readEndSec).getReader(); // Feed chunks to decoder in background with backpressure diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index dcfcc3e..d007b30 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -157,17 +157,11 @@ export class VideoExporter { this.muxer = muxer; await muxer.initialize(); - const { effectiveDuration, totalFrames } = streamingDecoder.getExportMetrics( + const { totalFrames } = streamingDecoder.getExportMetrics( this.config.frameRate, this.config.trimRegions, this.config.speedRegions, ); - const readEndSec = Math.max(videoInfo.duration, videoInfo.streamDuration ?? 0) + 0.5; - - console.log("[VideoExporter] Original duration:", videoInfo.duration, "s"); - console.log("[VideoExporter] Effective duration:", effectiveDuration, "s"); - console.log("[VideoExporter] Total frames to export:", totalFrames); - console.log("[VideoExporter] Using streaming decode (web-demuxer + VideoDecoder)"); const frameDuration = 1_000_000 / this.config.frameRate; let frameIndex = 0; @@ -346,7 +340,7 @@ export class VideoExporter { this.config.videoUrl, this.config.trimRegions, this.config.speedRegions, - readEndSec, + videoInfo.duration, ); } } From 337838294d69bf43089c993127bf44641794ba13 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 18:06:01 -0500 Subject: [PATCH 080/152] fix: pass explicit range to packet scan read Some containers are truncated when read() has no end bound. Use container/stream duration + buffer as scan range, matching the same pattern used in decodeAll(). --- src/lib/exporter/streamingDecoder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 7e261ff..cc5ded5 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -230,9 +230,11 @@ export class StreamingVideoDecoder { // Scan video packets to find the true content boundary. // MediaRecorder (especially on Linux) writes unreliable container durations. // Packet timestamps are ground truth — no decode needed, just timestamp reads. + // Pass explicit range because some containers are truncated without one. + const scanEndSec = Math.max(mediaInfo.duration, videoStream?.duration ?? 0, 0) + 0.5; let maxPacketEndUs = 0; const scanReader = ( - this.demuxer.read("video") as ReadableStream + this.demuxer.read("video", 0, scanEndSec) as ReadableStream ).getReader(); try { while (true) { From 83ea025ed8168456ab3573d3397efbdeb6f5c235 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Sun, 12 Apr 2026 18:17:50 -0500 Subject: [PATCH 081/152] fix: handle NaN in zero-scan fallback and symmetric divergence check - validateDuration returns 0 instead of NaN when both container is NaN and scanned is zero - Use Math.abs for divergence check so container under-reporting is also corrected (not just over-reporting) --- src/lib/exporter/streamingDecoder.test.ts | 8 ++++++++ src/lib/exporter/streamingDecoder.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/exporter/streamingDecoder.test.ts b/src/lib/exporter/streamingDecoder.test.ts index e3a0ecc..55b9123 100644 --- a/src/lib/exporter/streamingDecoder.test.ts +++ b/src/lib/exporter/streamingDecoder.test.ts @@ -27,9 +27,17 @@ describe("validateDuration", () => { expect(validateDuration(15.0, 15.3)).toBe(15.0); }); + it("returns scanned duration when container under-reports beyond threshold", () => { + expect(validateDuration(10, 15.3)).toBe(15.3); + }); + it("returns container duration when scanned is zero (corrupted/empty file)", () => { expect(validateDuration(10, 0)).toBe(10); }); + + it("returns 0 when both container is NaN and scanned is zero", () => { + expect(validateDuration(NaN, 0)).toBe(0); + }); }); describe("shouldFailDecodeEndedEarly", () => { diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index cc5ded5..b0866f5 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -86,12 +86,12 @@ export function validateDuration(containerDuration: number, scannedDuration: num if (scannedDuration <= 0) { // Zero scanned duration means corrupted/empty file — fall back to container // (downstream shouldFailDecodeEndedEarly will catch truly empty files) - return Math.max(containerDuration, 0); + return Number.isFinite(containerDuration) ? Math.max(containerDuration, 0) : 0; } if (!Number.isFinite(containerDuration) || containerDuration <= 0) { return scannedDuration; } - if (containerDuration - scannedDuration > DURATION_DIVERGENCE_THRESHOLD_SEC) { + if (Math.abs(containerDuration - scannedDuration) > DURATION_DIVERGENCE_THRESHOLD_SEC) { return scannedDuration; } return containerDuration; From 61e895a75a1436a8b8985b4a975238b5b2ce519e Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Thu, 16 Apr 2026 14:18:40 -0500 Subject: [PATCH 082/152] fix: sanitize packet-scan range against NaN/Infinity duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mediaInfo.duration from web-demuxer can be NaN or Infinity on Chromium Linux (same MediaRecorder bug this PR otherwise addresses). That value flowed straight into Math.max + demuxer.read() as scanEndSec, producing an invalid range argument and breaking the ground-truth packet scan. Guard both mediaInfo.duration and videoStream.duration with Number.isFinite before Math.max; validateDuration() already handled the downstream use. Drop redundant WebDemuxer.read() / getDecoderConfig() type casts while here — the generics infer the chunk/config type from the media string literal, so the `as ReadableStream` and `as AudioDecoderConfig` are no-ops. --- src/lib/exporter/audioEncoder.ts | 11 +++++------ src/lib/exporter/streamingDecoder.ts | 13 +++++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index e373702..611ef5b 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -62,7 +62,7 @@ export class AudioProcessor { ): Promise { let audioConfig: AudioDecoderConfig; try { - audioConfig = (await demuxer.getDecoderConfig("audio")) as AudioDecoderConfig; + audioConfig = await demuxer.getDecoderConfig("audio"); } catch { console.warn("[AudioProcessor] No audio track found, skipping"); return; @@ -87,11 +87,10 @@ export class AudioProcessor { typeof readEndSec === "number" && Number.isFinite(readEndSec) ? Math.max(0, readEndSec) : undefined; - const audioStream = ( + const audioStream = safeReadEndSec !== undefined ? demuxer.read("audio", 0, safeReadEndSec) - : demuxer.read("audio") - ) as ReadableStream; + : demuxer.read("audio"); const reader = audioStream.getReader(); try { @@ -396,8 +395,8 @@ export class AudioProcessor { try { await demuxer.load(file); - const audioConfig = (await demuxer.getDecoderConfig("audio")) as AudioDecoderConfig; - const reader = (demuxer.read("audio") as ReadableStream).getReader(); + const audioConfig = await demuxer.getDecoderConfig("audio"); + const reader = demuxer.read("audio").getReader(); let isFirstChunk = true; try { diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index b0866f5..fca92e2 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -231,11 +231,16 @@ export class StreamingVideoDecoder { // MediaRecorder (especially on Linux) writes unreliable container durations. // Packet timestamps are ground truth — no decode needed, just timestamp reads. // Pass explicit range because some containers are truncated without one. - const scanEndSec = Math.max(mediaInfo.duration, videoStream?.duration ?? 0, 0) + 0.5; + // Sanitize because mediaInfo.duration can be NaN/Infinity (Chromium Linux bug), + // which would propagate into demuxer.read() as an invalid endpoint. + const containerDurationSec = Number.isFinite(mediaInfo.duration) ? mediaInfo.duration : 0; + const streamDurationSec = + typeof videoStream?.duration === "number" && Number.isFinite(videoStream.duration) + ? videoStream.duration + : 0; + const scanEndSec = Math.max(containerDurationSec, streamDurationSec, 0) + 0.5; let maxPacketEndUs = 0; - const scanReader = ( - this.demuxer.read("video", 0, scanEndSec) as ReadableStream - ).getReader(); + const scanReader = this.demuxer.read("video", 0, scanEndSec).getReader(); try { while (true) { const { done, value } = await scanReader.read(); From 4d4b08db07811e2c248f9e1bd45de457cee76e35 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Thu, 16 Apr 2026 14:31:51 -0500 Subject: [PATCH 083/152] fix: skip chained initial trims before recording starts Startup trim-skip only consulted the first active region at t=0, so back-to-back or overlapping trims starting at zero (e.g. [0,500ms] followed by [500ms,1000ms]) left the second region un-skipped. The in-flight tick loop would catch it, but MediaRecorder was already running by then, capturing up to one rAF frame of trimmed audio into the blob and shifting the downstream timeline. Loop findActiveTrimRegion from the advancing startPosition until no region matches or startPosition >= effectiveEnd, bounded by trimRegions.length for safety. Recompute initialSpeedRegion from the final startPosition so playbackRate reflects the true start point. --- src/lib/exporter/audioEncoder.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index 611ef5b..64d46d0 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -227,15 +227,18 @@ export class AudioProcessor { await audioContext.resume(); } - // Skip past any initial trim region before recording starts - // to avoid capturing trimmed audio during the first frames. + // Skip past any initial trim region(s) before recording starts to avoid + // capturing trimmed audio during the first rAF frames of playback. + // Loops to handle back-to-back or overlapping trims at t=0. + const effectiveEnd = validatedDurationSec ?? media.duration; let startPosition = 0; - const initialTrim = this.findActiveTrimRegion(0, trimRegions); - if (initialTrim) { - startPosition = initialTrim.endMs / 1000; + for (let i = 0; i <= trimRegions.length; i++) { + const activeTrim = this.findActiveTrimRegion(startPosition * 1000, trimRegions); + if (!activeTrim) break; + startPosition = activeTrim.endMs / 1000; + if (startPosition >= effectiveEnd) break; } - const effectiveEnd = validatedDurationSec ?? media.duration; if (startPosition >= effectiveEnd) { // All content is trimmed — return silent blob return new Blob([], { type: "audio/webm" }); From 0c01db7afa99f11e13624825135a6bdf5de7a993 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Thu, 16 Apr 2026 14:33:27 -0500 Subject: [PATCH 084/152] fix: fall back to unbounded packet scan when duration hints missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier NaN/Infinity guard collapsed both duration hints to 0 when the container reported invalid values, which turned scanEndSec into 0.5s. The packet scan then read only the first half-second, scannedDuration capped there, and validateDuration fell back to that wrong value for the entire export — exactly the Chromium Linux case this PR is meant to fix. Use a 24h sentinel as the read endpoint when no hint is usable. An explicit end is still required (some containers are truncated without one, per prior comment), but the sentinel is large enough to exceed any realistic recording so the scan reaches real EOF. --- src/lib/exporter/streamingDecoder.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index fca92e2..00d9f0b 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -71,6 +71,11 @@ const EARLY_DECODE_END_THRESHOLD_SEC = 1; const METADATA_TAIL_TOLERANCE_SEC = 1.5; const STREAM_DURATION_MATCH_TOLERANCE_SEC = 0.25; const DURATION_DIVERGENCE_THRESHOLD_SEC = 1.5; +// Fallback upper bound for the packet scan when no reliable duration hint is +// available. Explicit end is required (some containers are truncated without +// one), but the hint-derived bound would cap the scan prematurely when +// container/stream duration are missing or corrupt. +const SCAN_UNBOUNDED_FALLBACK_SEC = 24 * 60 * 60; /** * Validate container duration against actual packet timestamps. @@ -238,7 +243,9 @@ export class StreamingVideoDecoder { typeof videoStream?.duration === "number" && Number.isFinite(videoStream.duration) ? videoStream.duration : 0; - const scanEndSec = Math.max(containerDurationSec, streamDurationSec, 0) + 0.5; + const hintedDurationSec = Math.max(containerDurationSec, streamDurationSec, 0); + const scanEndSec = + hintedDurationSec > 0 ? hintedDurationSec + 0.5 : SCAN_UNBOUNDED_FALLBACK_SEC; let maxPacketEndUs = 0; const scanReader = this.demuxer.read("video", 0, scanEndSec).getReader(); try { From dd8c001f6d0a3b74379d78e39b514abf95e383ca Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Thu, 16 Apr 2026 14:49:27 -0500 Subject: [PATCH 085/152] refactor: require validatedDurationSec in AudioProcessor, drop fallbacks AudioProcessor.process and renderPitchPreservedTimelineAudio accepted validatedDurationSec as optional, so the speed-aware path fell back to media.duration when it was absent. HTMLMediaElement.duration can be Infinity for the same MediaRecorder/Chromium Linux containers this PR targets, which would make effectiveEnd and the playback stop checks unreliable. The only caller (VideoExporter.process) already threads streamingDecoder's validatedDuration through, so make the parameter required. Drop the media.duration fallback, the Number.isFinite guard on readEndSec, and the two `!== undefined` checks in the tick loop. While here: - Document that +0.5 on readEndSec mirrors streamingDecoder.decodeAll's read window so trim-only and speed-aware paths stay in sync. - Replace the unreachable silent-blob fallback at the end of renderPitchPreservedTimelineAudio with a loud invariant throw, so a broken recorder contract surfaces instead of yielding empty audio. --- src/lib/exporter/audioEncoder.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index 64d46d0..08cdaf1 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -19,9 +19,9 @@ export class AudioProcessor { demuxer: WebDemuxer, muxer: VideoMuxer, videoUrl: string, - trimRegions?: TrimRegion[], - speedRegions?: SpeedRegion[], - validatedDurationSec?: number, + trimRegions: TrimRegion[] | undefined, + speedRegions: SpeedRegion[] | undefined, + validatedDurationSec: number, ): Promise { const sortedTrims = trimRegions ? [...trimRegions].sort((a, b) => a.startMs - b.startMs) : []; const sortedSpeedRegions = speedRegions @@ -46,10 +46,9 @@ export class AudioProcessor { } // No speed edits: keep the original demux/decode/encode path with trim timestamp remap. - const readEndSec = - typeof validatedDurationSec === "number" && Number.isFinite(validatedDurationSec) - ? validatedDurationSec + 0.5 - : undefined; + // The +0.5s buffer mirrors streamingDecoder.decodeAll's read window so the trim-only + // and speed-aware paths agree on how far to read past the validated duration boundary. + const readEndSec = validatedDurationSec + 0.5; await this.processTrimOnlyAudio(demuxer, muxer, sortedTrims, readEndSec); } @@ -193,7 +192,7 @@ export class AudioProcessor { videoUrl: string, trimRegions: TrimRegion[], speedRegions: SpeedRegion[], - validatedDurationSec?: number, + validatedDurationSec: number, ): Promise { const media = document.createElement("audio"); media.src = videoUrl; @@ -230,7 +229,7 @@ export class AudioProcessor { // Skip past any initial trim region(s) before recording starts to avoid // capturing trimmed audio during the first rAF frames of playback. // Loops to handle back-to-back or overlapping trims at t=0. - const effectiveEnd = validatedDurationSec ?? media.duration; + const effectiveEnd = validatedDurationSec; let startPosition = 0; for (let i = 0; i <= trimRegions.length; i++) { const activeTrim = this.findActiveTrimRegion(startPosition * 1000, trimRegions); @@ -287,7 +286,7 @@ export class AudioProcessor { // Stop playback at validated duration — browser's media.duration // may be inflated from bad container metadata. - if (validatedDurationSec !== undefined && media.currentTime >= validatedDurationSec) { + if (media.currentTime >= validatedDurationSec) { media.pause(); cleanup(); resolve(); @@ -299,10 +298,7 @@ export class AudioProcessor { if (activeTrimRegion && !media.paused && !media.ended) { const skipToTime = activeTrimRegion.endMs / 1000; - if ( - skipToTime >= media.duration || - (validatedDurationSec !== undefined && skipToTime >= validatedDurationSec) - ) { + if (skipToTime >= media.duration || skipToTime >= validatedDurationSec) { media.pause(); cleanup(); resolve(); @@ -379,7 +375,10 @@ export class AudioProcessor { } if (!recordedBlobPromise) { - return new Blob([], { type: "audio/webm" }); + // Invariant: either an early return above fires, or startAudioRecording ran and + // populated recordedBlobPromise before the playback Promise resolved. Reaching + // here means that contract was broken — fail loud instead of returning silence. + throw new Error("Audio recorder finished without assigning recordedBlobPromise"); } const recordedBlob = await recordedBlobPromise; if (this.cancelled) { From 485c95b6720058d74b2b89ad84b320a337123b25 Mon Sep 17 00:00:00 2001 From: ekkoitac Date: Sat, 18 Apr 2026 08:52:26 +0800 Subject: [PATCH 086/152] resolve conflicts: adopt main's tutorial i18n restructuring The main branch has already applied the same tutorial.help key restructuring with slightly different intermediate values. Adopting main's version to resolve merge conflicts. --- src/i18n/locales/en/dialogs.json | 13 ++++++++----- src/i18n/locales/es/dialogs.json | 12 +++++++----- src/i18n/locales/zh-CN/dialogs.json | 12 +++++++----- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/i18n/locales/en/dialogs.json b/src/i18n/locales/en/dialogs.json index 66a33c2..a84b5fd 100644 --- a/src/i18n/locales/en/dialogs.json +++ b/src/i18n/locales/en/dialogs.json @@ -27,10 +27,11 @@ "triggerLabel": "How trimming works", "title": "How Trimming Works", "description": "Understanding how to cut out unwanted parts of your video.", - "explanation": "The Trim tool works by defining the segments you want to", - "explanationRemove": "remove", - "explanationCovered": "covered", - "explanationEnd": "by a red trim segment will be cut out when you export.", + "explanationBefore": "The Trim tool works by defining the segments you want to", + "remove": "remove", + "explanationMiddle": " — anything", + "covered": "covered", + "explanationAfter": "by a red trim segment will be cut out when you export.", "visualExample": "Visual Example", "removed": "REMOVED", "kept": "Kept", @@ -39,7 +40,9 @@ "part3": "Part 3", "finalVideo": "Final Video", "step1Title": "1. Add Trim", - "step1Description": "Press T or click the scissors icon to mark a section for removal.", + "step1DescriptionBefore": "Press ", + "step1DescriptionAfter": " or click the scissors icon to mark a section for removal.", + "step2Title": "2. Adjust", "step2Description": "Drag the edges of the red region to cover exactly what you want to cut out." }, diff --git a/src/i18n/locales/es/dialogs.json b/src/i18n/locales/es/dialogs.json index acf2a04..f8a5e63 100644 --- a/src/i18n/locales/es/dialogs.json +++ b/src/i18n/locales/es/dialogs.json @@ -27,10 +27,11 @@ "triggerLabel": "Cómo funciona el recorte", "title": "Cómo funciona el recorte", "description": "Aprende a eliminar las partes no deseadas de tu video.", - "explanation": "La herramienta de recorte funciona definiendo los segmentos que deseas", - "explanationRemove": "eliminar", - "explanationCovered": "cubierto", - "explanationEnd": "por un segmento rojo de recorte será eliminado al exportar.", + "explanationBefore": "La herramienta de recorte funciona definiendo los segmentos que deseas", + "remove": "eliminar", + "explanationMiddle": " — cualquier parte", + "covered": "cubierta", + "explanationAfter": "por un segmento rojo será eliminada al exportar.", "visualExample": "Ejemplo visual", "removed": "ELIMINADO", "kept": "Conservado", @@ -39,7 +40,8 @@ "part3": "Parte 3", "finalVideo": "Video final", "step1Title": "1. Agregar recorte", - "step1Description": "Presiona T o haz clic en el ícono de tijeras para marcar una sección a eliminar.", + "step1DescriptionBefore": "Presiona ", + "step1DescriptionAfter": " o haz clic en el ícono de tijeras para marcar una sección a eliminar.", "step2Title": "2. Ajustar", "step2Description": "Arrastra los bordes de la región roja para cubrir exactamente lo que deseas eliminar." }, diff --git a/src/i18n/locales/zh-CN/dialogs.json b/src/i18n/locales/zh-CN/dialogs.json index 3f181bc..0385b36 100644 --- a/src/i18n/locales/zh-CN/dialogs.json +++ b/src/i18n/locales/zh-CN/dialogs.json @@ -27,10 +27,11 @@ "triggerLabel": "剪辑功能说明", "title": "剪辑功能说明", "description": "了解如何剪掉视频中不需要的部分。", - "explanation": "剪辑工具通过定义您要", - "explanationRemove": "移除", - "explanationCovered": "覆盖", - "explanationEnd": "的片段来工作。被红色剪辑区域覆盖的部分将在导出时被剪掉。", + "explanationBefore": "剪辑工具通过定义您要", + "remove": "移除", + "explanationMiddle": "——任何被", + "covered": "覆盖", + "explanationAfter": "的红色剪辑区域部分将在导出时被剪掉。", "visualExample": "示例演示", "removed": "已移除", "kept": "保留", @@ -39,7 +40,8 @@ "part3": "第 3 部分", "finalVideo": "最终视频", "step1Title": "1. 添加剪辑", - "step1Description": "按 T 或点击剪刀图标来标记要移除的片段。", + "step1DescriptionBefore": "按", + "step1DescriptionAfter": "键或点击剪刀图标来标记要移除的片段。", "step2Title": "2. 调整", "step2Description": "拖动红色区域的边缘,精确覆盖您要剪掉的部分。" }, From d12f3980f9cc8841f074fb803ab1e83c6c346ab5 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Tue, 14 Apr 2026 23:39:13 +0200 Subject: [PATCH 087/152] fix: only read back frames from canvas if the OS is linux, work around not necessary for other OS' line win or darwin --- src/lib/exporter/frameRenderer.ts | 6 ++++- src/lib/exporter/videoExporter.ts | 44 ++++++++++++++++++------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index 80424b0..7dd93b3 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -187,8 +187,12 @@ export class FrameRenderer { this.compositeCanvas = document.createElement("canvas"); this.compositeCanvas.width = this.config.width; this.compositeCanvas.height = this.config.height; + + // On Linux, getImageData() is called frequently causing frequent CPU readback + const isLinux = (await window.electronAPI.getPlatform()) === "linux"; + this.compositeCtx = this.compositeCanvas.getContext("2d", { - willReadFrequently: false, + willReadFrequently: isLinux, }); if (!this.compositeCtx) { diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index dcfcc3e..61ad727 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -111,6 +111,8 @@ export class VideoExporter { this.cancelled = false; this.fatalEncoderError = null; + const platform = await window.electronAPI.getPlatform(); + try { const streamingDecoder = new StreamingVideoDecoder(); this.streamingDecoder = streamingDecoder; @@ -237,25 +239,29 @@ export class VideoExporter { const canvas = renderer.getCanvas(); - // Read raw pixels from the canvas instead of passing - // the canvas directly to VideoFrame. On some Linux - // systems the GPU shared-image path (EGL/Ozone) fails - // silently, producing empty frames. - const canvasCtx = canvas.getContext("2d")!; - const imageData = canvasCtx.getImageData(0, 0, canvas.width, canvas.height); - const exportFrame = new VideoFrame(imageData.data.buffer, { - format: "RGBA", - codedWidth: canvas.width, - codedHeight: canvas.height, - timestamp, - duration: frameDuration, - colorSpace: { - primaries: "bt709", - transfer: "iec61966-2-1", - matrix: "rgb", - fullRange: true, - }, - }); + let exportFrame: VideoFrame; + + // On some Linux systems the GPU shared-image path (EGL/Ozone) fails + // silently, producing empty frames, so we force a CPU readback instead. + if (platform === "linux") { + const canvasCtx = canvas.getContext("2d")!; + const imageData = canvasCtx.getImageData(0, 0, canvas.width, canvas.height); + exportFrame = new VideoFrame(imageData.data.buffer, { + format: "RGBA", + codedWidth: canvas.width, + codedHeight: canvas.height, + timestamp, + duration: frameDuration, + colorSpace: { + primaries: "bt709", + transfer: "iec61966-2-1", + matrix: "rgb", + fullRange: true, + }, + }); + } else { + exportFrame = new VideoFrame(canvas, { timestamp, duration: frameDuration }); + } while ( this.encoder && From 934f05cc800fa310f7cdbfe485068fa329b9d789 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Wed, 15 Apr 2026 11:22:56 +0200 Subject: [PATCH 088/152] fix: pass platform from video/gifExporter to FrameRenderer, skip readback also for canvas composition for non-linux --- src/lib/exporter/frameRenderer.ts | 12 ++++++++---- src/lib/exporter/gifExporter.ts | 4 ++++ src/lib/exporter/videoExporter.ts | 5 +++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index 7dd93b3..9b1cf6d 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -78,6 +78,7 @@ interface FrameRenderConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; + platform: string; } interface AnimationState { @@ -124,9 +125,11 @@ export class FrameRenderer { private smoothedAutoFocus: { cx: number; cy: number } | null = null; private prevAnimationTimeMs: number | null = null; private prevTargetProgress = 0; + private isLinux = false; constructor(config: FrameRenderConfig) { this.config = config; + this.isLinux = config.platform === "linux"; this.animationState = { scale: 1, focusX: DEFAULT_FOCUS.cx, @@ -189,10 +192,8 @@ export class FrameRenderer { this.compositeCanvas.height = this.config.height; // On Linux, getImageData() is called frequently causing frequent CPU readback - const isLinux = (await window.electronAPI.getPlatform()) === "linux"; - this.compositeCtx = this.compositeCanvas.getContext("2d", { - willReadFrequently: isLinux, + willReadFrequently: this.isLinux, }); if (!this.compositeCtx) { @@ -730,7 +731,10 @@ export class FrameRenderer { private compositeWithShadows(webcamFrame?: VideoFrame | null): void { if (!this.compositeCanvas || !this.compositeCtx || !this.app) return; - const videoCanvas = this.readbackVideoCanvas(); + const videoCanvas = this.isLinux + ? this.readbackVideoCanvas() + : (this.app.canvas as HTMLCanvasElement); + const ctx = this.compositeCtx; const w = this.compositeCanvas.width; const h = this.compositeCanvas.height; diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 58ed693..81c540e 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -115,7 +115,10 @@ export class GifExporter { async export(): Promise { let webcamFrameQueue: AsyncVideoFrameQueue | null = null; + try { + const platform = await window.electronAPI.getPlatform(); + this.cleanup(); this.cancelled = false; @@ -153,6 +156,7 @@ export class GifExporter { previewWidth: this.config.previewWidth, previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, + platform, }); await this.renderer.initialize(); diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 61ad727..13c0696 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -111,9 +111,9 @@ export class VideoExporter { this.cancelled = false; this.fatalEncoderError = null; - const platform = await window.electronAPI.getPlatform(); - try { + const platform = await window.electronAPI.getPlatform(); + const streamingDecoder = new StreamingVideoDecoder(); this.streamingDecoder = streamingDecoder; const videoInfo = await streamingDecoder.loadMetadata(this.config.videoUrl); @@ -148,6 +148,7 @@ export class VideoExporter { previewWidth: this.config.previewWidth, previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, + platform, }); this.renderer = renderer; await renderer.initialize(); From 2f24038cb58c1d7852389f8ebbfda978203f9b97 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Thu, 16 Apr 2026 17:33:05 +0200 Subject: [PATCH 089/152] fix: use existing getPlatform() so the OS based CPU readback check also works in the browser --- src/lib/exporter/gifExporter.ts | 3 ++- src/lib/exporter/videoExporter.ts | 3 ++- src/utils/platformUtils.ts | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 81c540e..46ac6a0 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -8,6 +8,7 @@ import type { WebcamSizePreset, ZoomRegion, } from "@/components/video-editor/types"; +import { getPlatform } from "@/utils/platformUtils"; import { AsyncVideoFrameQueue } from "./asyncVideoFrameQueue"; import { FrameRenderer } from "./frameRenderer"; import { StreamingVideoDecoder } from "./streamingDecoder"; @@ -117,7 +118,7 @@ export class GifExporter { let webcamFrameQueue: AsyncVideoFrameQueue | null = null; try { - const platform = await window.electronAPI.getPlatform(); + const platform = await getPlatform(); this.cleanup(); this.cancelled = false; diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 13c0696..7662d93 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -7,6 +7,7 @@ import type { WebcamSizePreset, ZoomRegion, } from "@/components/video-editor/types"; +import { getPlatform } from "@/utils/platformUtils"; import { AsyncVideoFrameQueue } from "./asyncVideoFrameQueue"; import { AudioProcessor } from "./audioEncoder"; import { FrameRenderer } from "./frameRenderer"; @@ -112,7 +113,7 @@ export class VideoExporter { this.fatalEncoderError = null; try { - const platform = await window.electronAPI.getPlatform(); + const platform = await getPlatform(); const streamingDecoder = new StreamingVideoDecoder(); this.streamingDecoder = streamingDecoder; diff --git a/src/utils/platformUtils.ts b/src/utils/platformUtils.ts index 37d5a6d..2fb57e1 100644 --- a/src/utils/platformUtils.ts +++ b/src/utils/platformUtils.ts @@ -3,7 +3,7 @@ let cachedPlatform: string | null = null; /** * Gets the current platform from Electron */ -const getPlatform = async (): Promise => { +export const getPlatform = async (): Promise => { if (cachedPlatform) return cachedPlatform; try { @@ -14,9 +14,14 @@ const getPlatform = async (): Promise => { console.warn("Failed to get platform from Electron, falling back to navigator:", error); // Fallback for development/testing let fallbackPlatform = "win32"; - if (typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform)) { - fallbackPlatform = "darwin"; + if (typeof navigator !== "undefined") { + if (/Mac|iPhone|iPad|iPod/.test(navigator.platform)) { + fallbackPlatform = "darwin"; + } else if (/Linux/.test(navigator.platform)) { + fallbackPlatform = "linux"; + } } + cachedPlatform = fallbackPlatform; return fallbackPlatform; } From 9e4ec790f30fdde2d5fbb00fd0ac2ed1349a9bad Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Sat, 18 Apr 2026 11:32:42 +0200 Subject: [PATCH 090/152] chore: fix linting issue --- scripts/i18n-check.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index ca5cb51..699ae9e 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -11,7 +11,6 @@ import path from "node:path"; const LOCALES_DIR = path.resolve("src/i18n/locales"); const BASE_LOCALE = "en"; -const COMPARE_LOCALES = ["zh-CN", "zh-TW", "es", "tr", "ko-KR"]; function getKeys(obj, prefix = "") { const keys = []; From 721e8f47596ce3f5809b902c74841e6801050cf0 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 18 Apr 2026 21:37:16 +0800 Subject: [PATCH 091/152] Fix lint, type check errors, and apply CodeRabbit review feedback - Remove trailing comma in SUPPORTED_LOCALES that caused Locale type to include undefined, fixing all downstream type errors - Remove unused webcamSizePreset from useMemo dependency array - Use parsed.toString() instead of raw url in shell.openExternal per Electron security best practice Co-Authored-By: Claude Opus 4.7 --- electron/ipc/handlers.ts | 2 +- src/components/video-editor/VideoEditor.tsx | 1 - src/i18n/config.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 6aae4b4..5dffc84 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -645,7 +645,7 @@ export function registerIpcHandlers( return { success: false, error: `Unsupported URL scheme: ${parsed.protocol}` }; } - await shell.openExternal(url); + await shell.openExternal(parsed.toString()); return { success: true }; } catch (error) { console.error("Failed to open URL:", error); diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 88c3aae..49ff1b5 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -484,7 +484,6 @@ export default function VideoEditor() { aspectRatio, webcamLayoutPreset, webcamMaskShape, - webcamSizePreset, webcamPosition, exportQuality, exportFormat, diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 636d727..c576920 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,5 +1,5 @@ export const DEFAULT_LOCALE = "en" as const; -export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", , "fr", "tr"] as const; +export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", "fr", "tr"] as const; export const I18N_NAMESPACES = [ "common", "dialogs", From d22c4190cf7a98cce471b33b76c7ce7a6d25cbb1 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 18 Apr 2026 11:05:33 -0700 Subject: [PATCH 092/152] fix --- src/components/video-editor/VideoEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index e2973cf..6d21d13 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -506,6 +506,7 @@ export default function VideoEditor() { gifSizePreset, videoPath, t, + webcamSizePreset, ], ); From 33eb245aeadff0d3fa8af262ea532bc80ff19d6e Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 18 Apr 2026 11:29:12 -0700 Subject: [PATCH 093/152] codeowner --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..cfee36d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @siddharthvaddem From dc74db13ada615119441da49ced0acdfc9ebbe65 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 18 Apr 2026 11:36:59 -0700 Subject: [PATCH 094/152] test --- src/index.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.css b/src/index.css index 694f8bd..4266c51 100644 --- a/src/index.css +++ b/src/index.css @@ -2,6 +2,8 @@ @tailwind components; @tailwind utilities; + + @layer base { :root { --background: 0 0% 100%; From 63c850bc08ba6c6e1afebdd2ca19861e0bbe8d25 Mon Sep 17 00:00:00 2001 From: Aaryash Khalkar <91302334+imAaryash@users.noreply.github.com> Date: Sun, 19 Apr 2026 05:47:52 +0530 Subject: [PATCH 095/152] Change pull_request to pull_request_target in workflow --- .github/workflows/discord.yaml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/discord.yaml b/.github/workflows/discord.yaml index 97d23e7..0a96b5f 100644 --- a/.github/workflows/discord.yaml +++ b/.github/workflows/discord.yaml @@ -1,7 +1,7 @@ name: PR to Discord Forum on: - pull_request: + pull_request_target: types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, edited, labeled, unlabeled, closed] pull_request_review: types: [submitted] @@ -18,7 +18,10 @@ permissions: jobs: notify: - if: github.event_name != 'schedule' + if: github.event_name != 'schedule' && github.actor != 'github-actions[bot]' + concurrency: + group: discord-pr-sync-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} + cancel-in-progress: false runs-on: ubuntu-latest steps: - name: Sync PR activity to Discord forum thread @@ -145,7 +148,7 @@ jobs: } async function getPullRequest() { - if (context.eventName === "pull_request" || context.eventName === "pull_request_review") { + if (context.eventName === "pull_request_target" || context.eventName === "pull_request_review") { return context.payload.pull_request || null; } if (context.eventName === "issue_comment") { @@ -200,12 +203,7 @@ jobs: return; } - const isForkPr = !!pr.head?.repo?.fork; if (!webhookUrl) { - if (isForkPr) { - core.info("Skipping Discord sync: webhook secret is unavailable for fork PR events."); - return; - } core.setFailed( "Missing Discord webhook secret. Set either DISCORD_WEBHOOK_URL or DISCORD_PR_FORUM_WEBHOOK in repository secrets, or pass it explicitly if using reusable workflows." ); @@ -230,7 +228,7 @@ jobs: let threadId = extractThreadId(body); const shouldCreateThread = - context.eventName === "pull_request" && + context.eventName === "pull_request_target" && ["opened", "reopened", "ready_for_review"].includes(action) && !threadId; @@ -295,7 +293,7 @@ jobs: return; } - if (context.eventName === "pull_request" && ["edited", "labeled", "unlabeled", "ready_for_review", "converted_to_draft"].includes(action)) { + if (context.eventName === "pull_request_target" && ["edited", "labeled", "unlabeled", "ready_for_review", "converted_to_draft"].includes(action)) { const statusTag = desiredStatusTag({ draft: action === "converted_to_draft" ? true : pr.draft, reviewState, @@ -313,7 +311,7 @@ jobs: let updateMessage = null; let updateEmbed = null; - if (context.eventName === "pull_request") { + if (context.eventName === "pull_request_target") { if (action === "synchronize") { const { data: commits } = await github.rest.pulls.listCommits({ owner, repo, pull_number: number, per_page: 5 }); const list = commits.map((c) => `- \`${c.sha.slice(0, 7)}\` ${c.commit.message.split("\n")[0]}`).join("\n") || "- No commit details"; From 10463f882f80ca52b930246e2ffa3685811dc87e Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 18 Apr 2026 17:46:46 -0700 Subject: [PATCH 096/152] rm --- src/index.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.css b/src/index.css index 4266c51..694f8bd 100644 --- a/src/index.css +++ b/src/index.css @@ -2,8 +2,6 @@ @tailwind components; @tailwind utilities; - - @layer base { :root { --background: 0 0% 100%; From cfc6579e3705fb21d8394e989ec3af2667655f30 Mon Sep 17 00:00:00 2001 From: Aaryash Khalkar <91302334+imAaryash@users.noreply.github.com> Date: Sun, 19 Apr 2026 06:19:05 +0530 Subject: [PATCH 097/152] Improve Discord API error handling and webhook checks Refactor error handling for Discord API responses and improve webhook secret checks. --- .github/workflows/discord.yaml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/discord.yaml b/.github/workflows/discord.yaml index 0a96b5f..6da25d0 100644 --- a/.github/workflows/discord.yaml +++ b/.github/workflows/discord.yaml @@ -103,13 +103,19 @@ jobs: }) }); + const contentType = (response.headers.get("content-type") || "").toLowerCase(); + const text = await response.text(); + if (!response.ok) { - const text = await response.text(); throw new Error(`Discord API error ${response.status}: ${text}`); } - const text = await response.text(); - return text ? JSON.parse(text) : {}; + if (!text) return {}; + if (contentType.includes("application/json")) return JSON.parse(text); + + // Some proxy/CDN edge responses may return HTML with 2xx; avoid crashing on JSON parse. + core.warning(`Discord webhook returned non-JSON response (content-type: ${contentType || "unknown"}).`); + return {}; } async function patchDiscordThread(threadId, patchBody) { @@ -204,9 +210,15 @@ jobs: } if (!webhookUrl) { - core.setFailed( - "Missing Discord webhook secret. Set either DISCORD_WEBHOOK_URL or DISCORD_PR_FORUM_WEBHOOK in repository secrets, or pass it explicitly if using reusable workflows." - ); + const strictEvents = new Set(["pull_request_target", "workflow_dispatch"]); + const msg = + `Discord sync skipped: webhook secret unavailable for event '${context.eventName}'. ` + + "Set either DISCORD_WEBHOOK_URL or DISCORD_PR_FORUM_WEBHOOK in repository secrets."; + if (strictEvents.has(context.eventName)) { + core.setFailed(msg); + } else { + core.warning(msg); + } return; } From 1670db41a833c54454e2070d0ab81c3974de450d Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 15:53:31 +0530 Subject: [PATCH 098/152] feat:add countdown before record start --- electron/electron-env.d.ts | 4 + electron/ipc/handlers.ts | 43 +++++++++- electron/main.ts | 22 ++++- electron/preload.ts | 14 ++++ electron/windows.ts | 52 ++++++++++++ src/App.tsx | 19 +++-- src/components/launch/CountdownOverlay.tsx | 19 +++++ src/hooks/useScreenRecorder.ts | 98 +++++++++++++++++++++- src/main.tsx | 11 +++ 9 files changed, 271 insertions(+), 11 deletions(-) create mode 100644 src/components/launch/CountdownOverlay.tsx diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index b2a3720..178313d 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -135,6 +135,10 @@ interface Window { saveShortcuts: (shortcuts: unknown) => Promise<{ success: boolean; error?: string }>; hudOverlayHide: () => void; hudOverlayClose: () => void; + showCountdownOverlay: (value: number) => Promise; + setCountdownOverlayValue: (value: number) => Promise; + hideCountdownOverlay: () => Promise; + onCountdownOverlayValue: (callback: (value: number) => void) => () => void; setMicrophoneExpanded: (expanded: boolean) => void; setHasUnsavedChanges: (hasChanges: boolean) => void; onRequestSaveBeforeClose: (callback: () => Promise | boolean) => () => void; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index be20fcd..66ea746 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -352,11 +352,50 @@ function sampleCursorPoint() { export function registerIpcHandlers( createEditorWindow: () => void, createSourceSelectorWindow: () => BrowserWindow, + createCountdownOverlayWindow: () => BrowserWindow, getMainWindow: () => BrowserWindow | null, getSourceSelectorWindow: () => BrowserWindow | null, + getCountdownOverlayWindow: () => BrowserWindow | null, onRecordingStateChange?: (recording: boolean, sourceName: string) => void, switchToHud?: () => void, ) { + ipcMain.handle("countdown-overlay-show", async (_, value: number) => { + const win = getCountdownOverlayWindow() ?? createCountdownOverlayWindow(); + if (win.isDestroyed()) { + return; + } + + if (win.webContents.isLoading()) { + win.webContents.once("did-finish-load", () => { + if (!win.isDestroyed()) { + win.webContents.send("countdown-overlay-value", value); + win.showInactive(); + } + }); + } else { + win.webContents.send("countdown-overlay-value", value); + win.showInactive(); + } + }); + + ipcMain.handle("countdown-overlay-set-value", (_, value: number) => { + const win = getCountdownOverlayWindow(); + if (!win || win.isDestroyed()) { + return; + } + + win.webContents.send("countdown-overlay-value", value); + }); + + ipcMain.handle("countdown-overlay-hide", () => { + const win = getCountdownOverlayWindow(); + if (!win || win.isDestroyed()) { + return; + } + + win.hide(); + }); + ipcMain.handle("switch-to-hud", () => { if (switchToHud) switchToHud(); }); @@ -518,9 +557,8 @@ export function registerIpcHandlers( }); ipcMain.handle("read-binary-file", async (_, inputPath: string) => { - let normalizedPath: string | null = null; try { - normalizedPath = normalizeVideoSourcePath(inputPath); + const normalizedPath = normalizeVideoSourcePath(inputPath); if (!normalizedPath) { return { success: false, message: "Invalid file path" }; } @@ -545,7 +583,6 @@ export function registerIpcHandlers( success: false, message: "Failed to read binary file", error: String(error), - path: normalizedPath, }; } }); diff --git a/electron/main.ts b/electron/main.ts index c399fd0..6e0e0d5 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -14,7 +14,12 @@ import { } from "electron"; import { mainT, setMainLocale } from "./i18n"; import { registerIpcHandlers } from "./ipc/handlers"; -import { createEditorWindow, createHudOverlayWindow, createSourceSelectorWindow } from "./windows"; +import { + createCountdownOverlayWindow, + createEditorWindow, + createHudOverlayWindow, + createSourceSelectorWindow, +} from "./windows"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -60,6 +65,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL // Window references let mainWindow: BrowserWindow | null = null; let sourceSelectorWindow: BrowserWindow | null = null; +let countdownOverlayWindow: BrowserWindow | null = null; let tray: Tray | null = null; let selectedSourceName = ""; const isMac = process.platform === "darwin"; @@ -322,6 +328,18 @@ function createSourceSelectorWindowWrapper() { return sourceSelectorWindow; } +function createCountdownOverlayWindowWrapper() { + if (countdownOverlayWindow && !countdownOverlayWindow.isDestroyed()) { + return countdownOverlayWindow; + } + + countdownOverlayWindow = createCountdownOverlayWindow(); + countdownOverlayWindow.on("closed", () => { + countdownOverlayWindow = null; + }); + return countdownOverlayWindow; +} + // On macOS, applications and their menu bar stay active until the user quits // explicitly with Cmd + Q. app.on("window-all-closed", () => { @@ -386,8 +404,10 @@ app.whenReady().then(async () => { registerIpcHandlers( createEditorWindowWrapper, createSourceSelectorWindowWrapper, + createCountdownOverlayWindowWrapper, () => mainWindow, () => sourceSelectorWindow, + () => countdownOverlayWindow, (recording: boolean, sourceName: string) => { selectedSourceName = sourceName; if (!tray) createTray(); diff --git a/electron/preload.ts b/electron/preload.ts index eeca25c..15773df 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -130,6 +130,20 @@ contextBridge.exposeInMainWorld("electronAPI", { setHasUnsavedChanges: (hasChanges: boolean) => { ipcRenderer.send("set-has-unsaved-changes", hasChanges); }, + showCountdownOverlay: (value: number) => { + return ipcRenderer.invoke("countdown-overlay-show", value); + }, + setCountdownOverlayValue: (value: number) => { + return ipcRenderer.invoke("countdown-overlay-set-value", value); + }, + hideCountdownOverlay: () => { + return ipcRenderer.invoke("countdown-overlay-hide"); + }, + onCountdownOverlayValue: (callback: (value: number) => void) => { + const listener = (_event: unknown, value: number) => callback(value); + ipcRenderer.on("countdown-overlay-value", listener); + return () => ipcRenderer.removeListener("countdown-overlay-value", listener); + }, onRequestSaveBeforeClose: (callback: () => Promise | boolean) => { const listener = async () => { try { diff --git a/electron/windows.ts b/electron/windows.ts index dcd9f92..a5ed36f 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -177,3 +177,55 @@ export function createSourceSelectorWindow(): BrowserWindow { return win; } + +/** + * Creates a centered transparent countdown overlay window that sits above the + * HUD while recording pre-roll is running. + */ +export function createCountdownOverlayWindow(): BrowserWindow { + const { workArea } = screen.getPrimaryDisplay(); + const overlayWidth = 420; + const overlayHeight = 260; + + const win = new BrowserWindow({ + width: overlayWidth, + height: overlayHeight, + minWidth: overlayWidth, + maxWidth: overlayWidth, + minHeight: overlayHeight, + maxHeight: overlayHeight, + x: Math.round(workArea.x + (workArea.width - overlayWidth) / 2), + y: Math.round(workArea.y + (workArea.height - overlayHeight) / 2), + frame: false, + resizable: false, + alwaysOnTop: true, + skipTaskbar: true, + focusable: false, + transparent: true, + backgroundColor: "#00000000", + hasShadow: false, + show: !HEADLESS, + webPreferences: { + preload: path.join(__dirname, "preload.mjs"), + nodeIntegration: false, + contextIsolation: true, + backgroundThrottling: false, + }, + }); + + win.setIgnoreMouseEvents(true); + + if (process.platform === "darwin") { + win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + } + + if (VITE_DEV_SERVER_URL) { + win.loadURL(VITE_DEV_SERVER_URL + "?windowType=countdown-overlay"); + } else { + win.loadFile(path.join(RENDERER_DIST, "index.html"), { + query: { windowType: "countdown-overlay" }, + }); + } + + return win; +} diff --git a/src/App.tsx b/src/App.tsx index 9772ef8..a947c9e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { CountdownOverlay } from "./components/launch/CountdownOverlay.tsx"; import { LaunchWindow } from "./components/launch/LaunchWindow"; import { SourceSelector } from "./components/launch/SourceSelector"; import { Toaster } from "./components/ui/sonner"; @@ -9,13 +10,17 @@ import { ShortcutsProvider } from "./contexts/ShortcutsContext"; import { loadAllCustomFonts } from "./lib/customFonts"; export default function App() { - const [windowType, setWindowType] = useState(""); + const [windowType, setWindowType] = useState( + () => new URLSearchParams(window.location.search).get("windowType") || "", + ); useEffect(() => { - const params = new URLSearchParams(window.location.search); - const type = params.get("windowType") || ""; - setWindowType(type); - if (type === "hud-overlay" || type === "source-selector") { + const type = new URLSearchParams(window.location.search).get("windowType") || ""; + if (type !== windowType) { + setWindowType(type); + } + + if (type === "hud-overlay" || type === "source-selector" || type === "countdown-overlay") { document.body.style.background = "transparent"; document.documentElement.style.background = "transparent"; document.getElementById("root")?.style.setProperty("background", "transparent"); @@ -25,7 +30,7 @@ export default function App() { loadAllCustomFonts().catch((error) => { console.error("Failed to load custom fonts:", error); }); - }, []); + }, [windowType]); const content = (() => { switch (windowType) { @@ -33,6 +38,8 @@ export default function App() { return ; case "source-selector": return ; + case "countdown-overlay": + return ; case "editor": return ( diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx new file mode 100644 index 0000000..afe2cc9 --- /dev/null +++ b/src/components/launch/CountdownOverlay.tsx @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +export function CountdownOverlay() { + const [value, setValue] = useState(3); + + useEffect(() => { + const unsubscribe = window.electronAPI.onCountdownOverlayValue((nextValue) => { + setValue(nextValue); + }); + + return () => unsubscribe(); + }, []); + + return ( +
+
{value}
+
+ ); +} diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 5cbc54a..b1e0bb9 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -110,6 +110,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const allowAutoFinalize = useRef(false); const discardRecordingId = useRef(null); const restarting = useRef(false); + const countdownRunId = useRef(0); + const [countdownActive, setCountdownActive] = useState(false); const getRecordingDurationMs = useCallback(() => { const segmentDuration = @@ -335,6 +337,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return () => { if (cleanup) cleanup(); + countdownRunId.current += 1; + void window.electronAPI.hideCountdownOverlay(); allowAutoFinalize.current = false; restarting.current = false; discardRecordingId.current = null; @@ -365,6 +369,88 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; }, [teardownMedia]); + const cancelCountdown = () => { + countdownRunId.current += 1; + setCountdownActive(false); + void window.electronAPI.hideCountdownOverlay(); + }; + + const safeShowCountdownOverlay = async (value: number) => { + try { + await window.electronAPI.showCountdownOverlay(value); + return true; + } catch (error) { + console.warn("Failed to show countdown overlay:", error); + return false; + } + }; + + const safeSetCountdownOverlayValue = async (value: number) => { + try { + await window.electronAPI.setCountdownOverlayValue(value); + } catch (error) { + console.warn("Failed to update countdown overlay value:", error); + } + }; + + const safeHideCountdownOverlay = async () => { + try { + await window.electronAPI.hideCountdownOverlay(); + } catch (error) { + console.warn("Failed to hide countdown overlay:", error); + } + }; + + const startRecordCountdown = async () => { + if (countdownActive || recording) { + return; + } + + let selectedSource: ProcessedDesktopSource | null = null; + try { + selectedSource = await window.electronAPI.getSelectedSource(); + } catch (error) { + console.warn("Failed to read selected source before countdown:", error); + } + + if (!selectedSource) { + alert(t("recording.selectSource")); + return; + } + + const runId = countdownRunId.current + 1; + countdownRunId.current = runId; + setCountdownActive(true); + + try { + const values = [3, 2, 1]; + const overlayShown = await safeShowCountdownOverlay(values[0]); + + for (const value of values) { + if (countdownRunId.current !== runId) { + return; + } + + if (overlayShown && value !== values[0]) { + await safeSetCountdownOverlayValue(value); + } + + await new Promise((resolve) => window.setTimeout(resolve, 1000)); + } + + if (countdownRunId.current !== runId) { + return; + } + + await startRecording(); + } finally { + if (countdownRunId.current === runId) { + setCountdownActive(false); + } + await safeHideCountdownOverlay(); + } + }; + const startRecording = async () => { try { const selectedSource = await window.electronAPI.getSelectedSource(); @@ -635,7 +721,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; const toggleRecording = () => { - recording ? stopRecording.current() : startRecording(); + if (countdownActive) { + cancelCountdown(); + return; + } + + recording ? stopRecording.current() : void startRecordCountdown(); }; const restartRecording = async () => { @@ -698,6 +789,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }, [getRecordingDurationMs, paused, recording]); const cancelRecording = () => { + if (countdownActive) { + cancelCountdown(); + return; + } + const activeScreenRecorder = screenRecorder.current; if (!activeScreenRecorder || activeScreenRecorder.recorder.state !== "recording") return; diff --git a/src/main.tsx b/src/main.tsx index 670e2b6..365bdc7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,17 @@ import App from "./App.tsx"; import { I18nProvider } from "./contexts/I18nContext"; import "./index.css"; +const windowType = new URLSearchParams(window.location.search).get("windowType") || ""; +if ( + windowType === "hud-overlay" || + windowType === "source-selector" || + windowType === "countdown-overlay" +) { + document.body.style.background = "transparent"; + document.documentElement.style.background = "transparent"; + document.getElementById("root")?.style.setProperty("background", "transparent"); +} + ReactDOM.createRoot(document.getElementById("root")!).render( From 6b08a0a72a6c32f3c17d80e8741df940dad6d96d Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 18:27:00 +0530 Subject: [PATCH 099/152] fix:flickering, stale runs, macOS bugs provided by coderabbit and thread countdown token --- electron/electron-env.d.ts | 2 +- electron/ipc/handlers.ts | 46 ++++++++++++++++++---- electron/main.ts | 13 +++++- electron/preload.ts | 4 +- electron/windows.ts | 2 +- src/components/launch/CountdownOverlay.tsx | 8 +++- src/hooks/useScreenRecorder.ts | 34 ++++++++++++++-- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 178313d..4716472 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -138,7 +138,7 @@ interface Window { showCountdownOverlay: (value: number) => Promise; setCountdownOverlayValue: (value: number) => Promise; hideCountdownOverlay: () => Promise; - onCountdownOverlayValue: (callback: (value: number) => void) => () => void; + onCountdownOverlayValue: (callback: (value: number | null) => void) => () => void; setMicrophoneExpanded: (expanded: boolean) => void; setHasUnsavedChanges: (hasChanges: boolean) => void; onRequestSaveBeforeClose: (callback: () => Promise | boolean) => () => void; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 66ea746..fdabf6c 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -359,7 +359,30 @@ export function registerIpcHandlers( onRecordingStateChange?: (recording: boolean, sourceName: string) => void, switchToHud?: () => void, ) { - ipcMain.handle("countdown-overlay-show", async (_, value: number) => { + const countdownOverlayState = { + visible: false, + value: null as number | null, + }; + + const flushCountdownOverlayState = (win: BrowserWindow) => { + if (win.isDestroyed()) { + return; + } + + win.webContents.send("countdown-overlay-value", countdownOverlayState.value); + if (countdownOverlayState.visible && !win.isVisible()) { + setTimeout(() => { + if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) { + win.showInactive(); + } + }, 16); + } + }; + + ipcMain.handle("countdown-overlay-show", (_, value: number) => { + countdownOverlayState.visible = true; + countdownOverlayState.value = value; + const win = getCountdownOverlayWindow() ?? createCountdownOverlayWindow(); if (win.isDestroyed()) { return; @@ -368,38 +391,47 @@ export function registerIpcHandlers( if (win.webContents.isLoading()) { win.webContents.once("did-finish-load", () => { if (!win.isDestroyed()) { - win.webContents.send("countdown-overlay-value", value); - win.showInactive(); + flushCountdownOverlayState(win); } }); } else { - win.webContents.send("countdown-overlay-value", value); - win.showInactive(); + flushCountdownOverlayState(win); } }); ipcMain.handle("countdown-overlay-set-value", (_, value: number) => { + countdownOverlayState.value = value; + const win = getCountdownOverlayWindow(); if (!win || win.isDestroyed()) { return; } + if (win.webContents.isLoading()) { + return; + } + win.webContents.send("countdown-overlay-value", value); }); ipcMain.handle("countdown-overlay-hide", () => { + countdownOverlayState.visible = false; + countdownOverlayState.value = null; + const win = getCountdownOverlayWindow(); if (!win || win.isDestroyed()) { return; } - win.hide(); + if (!win.webContents.isLoading()) { + win.webContents.send("countdown-overlay-value", countdownOverlayState.value); + } }); ipcMain.handle("switch-to-hud", () => { if (switchToHud) switchToHud(); }); - ipcMain.handle("start-new-recording", async () => { + ipcMain.handle("start-new-recording", () => { try { setCurrentRecordingSessionState(null); if (switchToHud) { diff --git a/electron/main.ts b/electron/main.ts index 6e0e0d5..ad0a33f 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -349,8 +349,17 @@ app.on("window-all-closed", () => { app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); + const hasVisibleWindow = BrowserWindow.getAllWindows().some((window) => { + if (window.isDestroyed() || !window.isVisible()) { + return false; + } + + const url = window.webContents.getURL(); + const isCountdownOverlayWindow = url.includes("windowType=countdown-overlay"); + return !isCountdownOverlayWindow; + }); + if (!hasVisibleWindow) { + showMainWindow(); } }); diff --git a/electron/preload.ts b/electron/preload.ts index 15773df..77b7d99 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -139,8 +139,8 @@ contextBridge.exposeInMainWorld("electronAPI", { hideCountdownOverlay: () => { return ipcRenderer.invoke("countdown-overlay-hide"); }, - onCountdownOverlayValue: (callback: (value: number) => void) => { - const listener = (_event: unknown, value: number) => callback(value); + onCountdownOverlayValue: (callback: (value: number | null) => void) => { + const listener = (_event: unknown, value: number | null) => callback(value); ipcRenderer.on("countdown-overlay-value", listener); return () => ipcRenderer.removeListener("countdown-overlay-value", listener); }, diff --git a/electron/windows.ts b/electron/windows.ts index a5ed36f..daa6e5e 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -204,7 +204,7 @@ export function createCountdownOverlayWindow(): BrowserWindow { transparent: true, backgroundColor: "#00000000", hasShadow: false, - show: !HEADLESS, + show: false, webPreferences: { preload: path.join(__dirname, "preload.mjs"), nodeIntegration: false, diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx index afe2cc9..a3a149d 100644 --- a/src/components/launch/CountdownOverlay.tsx +++ b/src/components/launch/CountdownOverlay.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; export function CountdownOverlay() { - const [value, setValue] = useState(3); + const [value, setValue] = useState(null); useEffect(() => { const unsubscribe = window.electronAPI.onCountdownOverlayValue((nextValue) => { @@ -13,7 +13,11 @@ export function CountdownOverlay() { return (
-
{value}
+ {value === null ? null : ( +
+ {value} +
+ )}
); } diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index b1e0bb9..6a17c1e 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -401,6 +401,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } }; + const isCountdownRunActive = (runId?: number) => + runId === undefined || countdownRunId.current === runId; + const startRecordCountdown = async () => { if (countdownActive || recording) { return; @@ -442,16 +445,16 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } - await startRecording(); + await startRecording(runId); } finally { if (countdownRunId.current === runId) { setCountdownActive(false); + await safeHideCountdownOverlay(); } - await safeHideCountdownOverlay(); } }; - const startRecording = async () => { + const startRecording = async (countdownRunToken?: number) => { try { const selectedSource = await window.electronAPI.getSelectedSource(); if (!selectedSource) { @@ -459,6 +462,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + let screenMediaStream: MediaStream; const videoConstraints = { @@ -499,6 +507,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } screenStream.current = screenMediaStream; + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + if (microphoneEnabled) { try { microphoneStream.current = await navigator.mediaDevices.getUserMedia({ @@ -523,6 +536,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + if (webcamEnabled) { try { webcamStream.current = await navigator.mediaDevices.getUserMedia({ @@ -551,6 +569,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + stream.current = new MediaStream(); const videoTrack = screenMediaStream.getVideoTracks()[0]; if (!videoTrack) { @@ -610,6 +633,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { ); const hasAudio = stream.current.getAudioTracks().length > 0; + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + screenRecorder.current = createRecorderHandle(stream.current, { mimeType, videoBitsPerSecond, From ea68e4cfc3b979afe109367f6ab7405c2783edde Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 19:22:57 +0530 Subject: [PATCH 100/152] fix:prevent stale countdown IPC updates from repainting overlay --- electron/electron-env.d.ts | 6 ++-- electron/ipc/handlers.ts | 16 +++++++++-- electron/preload.ts | 12 ++++---- src/components/launch/CountdownOverlay.tsx | 5 +++- src/hooks/useScreenRecorder.ts | 32 ++++++++++++++-------- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 4716472..e20cf7f 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -135,9 +135,9 @@ interface Window { saveShortcuts: (shortcuts: unknown) => Promise<{ success: boolean; error?: string }>; hudOverlayHide: () => void; hudOverlayClose: () => void; - showCountdownOverlay: (value: number) => Promise; - setCountdownOverlayValue: (value: number) => Promise; - hideCountdownOverlay: () => Promise; + showCountdownOverlay: (value: number, runId: number) => Promise; + setCountdownOverlayValue: (value: number, runId: number) => Promise; + hideCountdownOverlay: (runId: number) => Promise; onCountdownOverlayValue: (callback: (value: number | null) => void) => () => void; setMicrophoneExpanded: (expanded: boolean) => void; setHasUnsavedChanges: (hasChanges: boolean) => void; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index fdabf6c..729408f 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -362,6 +362,7 @@ export function registerIpcHandlers( const countdownOverlayState = { visible: false, value: null as number | null, + activeRunId: null as number | null, }; const flushCountdownOverlayState = (win: BrowserWindow) => { @@ -379,7 +380,8 @@ export function registerIpcHandlers( } }; - ipcMain.handle("countdown-overlay-show", (_, value: number) => { + ipcMain.handle("countdown-overlay-show", (_, value: number, runId: number) => { + countdownOverlayState.activeRunId = runId; countdownOverlayState.visible = true; countdownOverlayState.value = value; @@ -399,7 +401,11 @@ export function registerIpcHandlers( } }); - ipcMain.handle("countdown-overlay-set-value", (_, value: number) => { + ipcMain.handle("countdown-overlay-set-value", (_, value: number, runId: number) => { + if (countdownOverlayState.activeRunId !== runId || !countdownOverlayState.visible) { + return; + } + countdownOverlayState.value = value; const win = getCountdownOverlayWindow(); @@ -414,7 +420,11 @@ export function registerIpcHandlers( win.webContents.send("countdown-overlay-value", value); }); - ipcMain.handle("countdown-overlay-hide", () => { + ipcMain.handle("countdown-overlay-hide", (_, runId: number) => { + if (countdownOverlayState.activeRunId !== runId) { + return; + } + countdownOverlayState.visible = false; countdownOverlayState.value = null; diff --git a/electron/preload.ts b/electron/preload.ts index 77b7d99..6aa066f 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -130,14 +130,14 @@ contextBridge.exposeInMainWorld("electronAPI", { setHasUnsavedChanges: (hasChanges: boolean) => { ipcRenderer.send("set-has-unsaved-changes", hasChanges); }, - showCountdownOverlay: (value: number) => { - return ipcRenderer.invoke("countdown-overlay-show", value); + showCountdownOverlay: (value: number, runId: number) => { + return ipcRenderer.invoke("countdown-overlay-show", value, runId); }, - setCountdownOverlayValue: (value: number) => { - return ipcRenderer.invoke("countdown-overlay-set-value", value); + setCountdownOverlayValue: (value: number, runId: number) => { + return ipcRenderer.invoke("countdown-overlay-set-value", value, runId); }, - hideCountdownOverlay: () => { - return ipcRenderer.invoke("countdown-overlay-hide"); + hideCountdownOverlay: (runId: number) => { + return ipcRenderer.invoke("countdown-overlay-hide", runId); }, onCountdownOverlayValue: (callback: (value: number | null) => void) => { const listener = (_event: unknown, value: number | null) => callback(value); diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx index a3a149d..1ee5c97 100644 --- a/src/components/launch/CountdownOverlay.tsx +++ b/src/components/launch/CountdownOverlay.tsx @@ -14,7 +14,10 @@ export function CountdownOverlay() { return (
{value === null ? null : ( -
+
{value}
)} diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 6a17c1e..51c57dd 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -336,9 +336,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } return () => { + const activeRunId = countdownRunId.current; if (cleanup) cleanup(); countdownRunId.current += 1; - void window.electronAPI.hideCountdownOverlay(); + void window.electronAPI.hideCountdownOverlay(activeRunId); allowAutoFinalize.current = false; restarting.current = false; discardRecordingId.current = null; @@ -370,14 +371,15 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }, [teardownMedia]); const cancelCountdown = () => { + const activeRunId = countdownRunId.current; countdownRunId.current += 1; setCountdownActive(false); - void window.electronAPI.hideCountdownOverlay(); + void window.electronAPI.hideCountdownOverlay(activeRunId); }; - const safeShowCountdownOverlay = async (value: number) => { + const safeShowCountdownOverlay = async (value: number, runId: number) => { try { - await window.electronAPI.showCountdownOverlay(value); + await window.electronAPI.showCountdownOverlay(value, runId); return true; } catch (error) { console.warn("Failed to show countdown overlay:", error); @@ -385,17 +387,17 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } }; - const safeSetCountdownOverlayValue = async (value: number) => { + const safeSetCountdownOverlayValue = async (value: number, runId: number) => { try { - await window.electronAPI.setCountdownOverlayValue(value); + await window.electronAPI.setCountdownOverlayValue(value, runId); } catch (error) { console.warn("Failed to update countdown overlay value:", error); } }; - const safeHideCountdownOverlay = async () => { + const safeHideCountdownOverlay = async (runId: number) => { try { - await window.electronAPI.hideCountdownOverlay(); + await window.electronAPI.hideCountdownOverlay(runId); } catch (error) { console.warn("Failed to hide countdown overlay:", error); } @@ -427,7 +429,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { try { const values = [3, 2, 1]; - const overlayShown = await safeShowCountdownOverlay(values[0]); + const overlayShown = await safeShowCountdownOverlay(values[0], runId); + + if (countdownRunId.current !== runId) { + return; + } for (const value of values) { if (countdownRunId.current !== runId) { @@ -435,7 +441,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } if (overlayShown && value !== values[0]) { - await safeSetCountdownOverlayValue(value); + await safeSetCountdownOverlayValue(value, runId); + + if (countdownRunId.current !== runId) { + return; + } } await new Promise((resolve) => window.setTimeout(resolve, 1000)); @@ -449,7 +459,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } finally { if (countdownRunId.current === runId) { setCountdownActive(false); - await safeHideCountdownOverlay(); + await safeHideCountdownOverlay(runId); } } }; From d04bab732bac5bb41f5764acc077d4ba03d12eff Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 19:44:51 +0530 Subject: [PATCH 101/152] prioritize recording stop over countdown cancel --- src/hooks/useScreenRecorder.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 51c57dd..44dece0 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -759,12 +759,17 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; const toggleRecording = () => { + if (recording) { + stopRecording.current(); + return; + } + if (countdownActive) { cancelCountdown(); return; } - recording ? stopRecording.current() : void startRecordCountdown(); + void startRecordCountdown(); }; const restartRecording = async () => { @@ -827,19 +832,23 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }, [getRecordingDurationMs, paused, recording]); const cancelRecording = () => { + const activeScreenRecorder = screenRecorder.current; + if ( + activeScreenRecorder?.recorder.state === "recording" || + activeScreenRecorder?.recorder.state === "paused" + ) { + const activeRecordingId = recordingId.current; + discardRecordingId.current = activeRecordingId; + allowAutoFinalize.current = false; + + stopRecording.current(); + return; + } + if (countdownActive) { cancelCountdown(); return; } - - const activeScreenRecorder = screenRecorder.current; - if (!activeScreenRecorder || activeScreenRecorder.recorder.state !== "recording") return; - - const activeRecordingId = recordingId.current; - discardRecordingId.current = activeRecordingId; - allowAutoFinalize.current = false; - - stopRecording.current(); }; return { From 331e126d3c4b37d9c0f1948805b9439d72b2e1b9 Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 20:10:56 +0530 Subject: [PATCH 102/152] fix:handle hideCountdownOverlay rejections in cleanup/cancel paths. --- src/hooks/useScreenRecorder.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 44dece0..f208013 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -339,7 +339,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const activeRunId = countdownRunId.current; if (cleanup) cleanup(); countdownRunId.current += 1; - void window.electronAPI.hideCountdownOverlay(activeRunId); + void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => { + console.warn("Failed to hide countdown overlay during cleanup:", error); + }); allowAutoFinalize.current = false; restarting.current = false; discardRecordingId.current = null; @@ -374,7 +376,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const activeRunId = countdownRunId.current; countdownRunId.current += 1; setCountdownActive(false); - void window.electronAPI.hideCountdownOverlay(activeRunId); + void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => { + console.warn("Failed to hide countdown overlay during cancel:", error); + }); }; const safeShowCountdownOverlay = async (value: number, runId: number) => { From 3ba9e901c9f17e16638c3cf542e28e26ca3305ba Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Thu, 16 Apr 2026 20:36:25 +0530 Subject: [PATCH 103/152] fix:Claim the countdown run before the first await. --- src/hooks/useScreenRecorder.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index f208013..7c9112d 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -415,6 +415,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } + const runId = countdownRunId.current + 1; + countdownRunId.current = runId; + setCountdownActive(true); + let selectedSource: ProcessedDesktopSource | null = null; try { selectedSource = await window.electronAPI.getSelectedSource(); @@ -422,14 +426,17 @@ export function useScreenRecorder(): UseScreenRecorderReturn { console.warn("Failed to read selected source before countdown:", error); } - if (!selectedSource) { - alert(t("recording.selectSource")); + if (!isCountdownRunActive(runId)) { return; } - const runId = countdownRunId.current + 1; - countdownRunId.current = runId; - setCountdownActive(true); + if (!selectedSource) { + if (countdownRunId.current === runId) { + setCountdownActive(false); + } + alert(t("recording.selectSource")); + return; + } try { const values = [3, 2, 1]; From 65b9d189e83998b01ac52f6f5468c5c2c84ea346 Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Fri, 17 Apr 2026 10:55:03 +0530 Subject: [PATCH 104/152] fix:improve ui of the countdown by adding a low opacity circle background --- src/components/launch/CountdownOverlay.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx index 1ee5c97..54ec8e8 100644 --- a/src/components/launch/CountdownOverlay.tsx +++ b/src/components/launch/CountdownOverlay.tsx @@ -14,11 +14,13 @@ export function CountdownOverlay() { return (
{value === null ? null : ( -
- {value} +
+
+ {value} +
)}
From 7e02856836ed52b490f9b07eb031b3eae750e59a Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Sun, 19 Apr 2026 12:35:19 +0530 Subject: [PATCH 105/152] fix:hide handler actually hides window instead of just clearing value --- electron/ipc/handlers.ts | 81 ++++++++++++++++++++-- src/components/launch/CountdownOverlay.tsx | 20 +++--- src/hooks/useScreenRecorder.ts | 9 ++- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 729408f..261d93f 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -359,10 +359,37 @@ export function registerIpcHandlers( onRecordingStateChange?: (recording: boolean, sourceName: string) => void, switchToHud?: () => void, ) { + const supportsWindowOpacity = process.platform !== "linux"; const countdownOverlayState = { visible: false, value: null as number | null, activeRunId: null as number | null, + hideCommitId: 0, + hideCommitTimer: null as ReturnType | null, + }; + const COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS = 1200; + + const clearCountdownOverlayHideCommit = () => { + if (countdownOverlayState.hideCommitTimer) { + clearTimeout(countdownOverlayState.hideCommitTimer); + countdownOverlayState.hideCommitTimer = null; + } + }; + + const commitCountdownOverlayHide = (win: BrowserWindow, hideCommitId: number) => { + if (win.isDestroyed()) { + return; + } + + if (countdownOverlayState.visible || countdownOverlayState.hideCommitId !== hideCommitId) { + return; + } + + win.hide(); + if (supportsWindowOpacity) { + // Reset baseline opacity for the next show cycle. + win.setOpacity(1); + } }; const flushCountdownOverlayState = (win: BrowserWindow) => { @@ -370,14 +397,35 @@ export function registerIpcHandlers( return; } + clearCountdownOverlayHideCommit(); win.webContents.send("countdown-overlay-value", countdownOverlayState.value); - if (countdownOverlayState.visible && !win.isVisible()) { - setTimeout(() => { - if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) { - win.showInactive(); - } - }, 16); + if (!countdownOverlayState.visible) { + return; } + + if (win.isVisible()) { + if (supportsWindowOpacity) { + win.setOpacity(1); + } + return; + } + + setTimeout(() => { + if (!win.isDestroyed() && countdownOverlayState.visible && !win.isVisible()) { + if (supportsWindowOpacity) { + win.setOpacity(0); + } + win.showInactive(); + + if (supportsWindowOpacity) { + setTimeout(() => { + if (!win.isDestroyed() && countdownOverlayState.visible && win.isVisible()) { + win.setOpacity(1); + } + }, 0); + } + } + }, 16); }; ipcMain.handle("countdown-overlay-show", (_, value: number, runId: number) => { @@ -426,16 +474,35 @@ export function registerIpcHandlers( } countdownOverlayState.visible = false; - countdownOverlayState.value = null; + countdownOverlayState.hideCommitId += 1; + const hideCommitId = countdownOverlayState.hideCommitId; + clearCountdownOverlayHideCommit(); const win = getCountdownOverlayWindow(); if (!win || win.isDestroyed()) { + countdownOverlayState.value = null; return; } + if (supportsWindowOpacity) { + // Hide visually immediately to avoid hide/show compositor flashes on rapid restart. + win.setOpacity(0); + } + + countdownOverlayState.value = null; if (!win.webContents.isLoading()) { win.webContents.send("countdown-overlay-value", countdownOverlayState.value); } + + if (!supportsWindowOpacity) { + win.hide(); + return; + } + + countdownOverlayState.hideCommitTimer = setTimeout(() => { + countdownOverlayState.hideCommitTimer = null; + commitCountdownOverlayHide(win, hideCommitId); + }, COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS); }); ipcMain.handle("switch-to-hud", () => { diff --git a/src/components/launch/CountdownOverlay.tsx b/src/components/launch/CountdownOverlay.tsx index 54ec8e8..71d12c5 100644 --- a/src/components/launch/CountdownOverlay.tsx +++ b/src/components/launch/CountdownOverlay.tsx @@ -11,18 +11,20 @@ export function CountdownOverlay() { return () => unsubscribe(); }, []); + if (value === null) { + return null; + } + return (
- {value === null ? null : ( -
-
- {value} -
+
+
+ {value}
- )} +
); } diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 7c9112d..6644f42 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -339,9 +339,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const activeRunId = countdownRunId.current; if (cleanup) cleanup(); countdownRunId.current += 1; - void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => { - console.warn("Failed to hide countdown overlay during cleanup:", error); - }); + void safeHideCountdownOverlay(activeRunId); allowAutoFinalize.current = false; restarting.current = false; discardRecordingId.current = null; @@ -635,6 +633,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn { ); } + if (!isCountdownRunActive(countdownRunToken)) { + teardownMedia(); + return; + } + let { width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, From 4a65ab81711a12f637245bc249edab4636568a2c Mon Sep 17 00:00:00 2001 From: Galactic99 Date: Sun, 19 Apr 2026 12:57:17 +0530 Subject: [PATCH 106/152] chore:safewrapper consistency and hide countdown overlay before starting recording setup. --- src/App.tsx | 4 +++- src/hooks/useScreenRecorder.ts | 27 +++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a947c9e..4045b5d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,12 +25,14 @@ export default function App() { document.documentElement.style.background = "transparent"; document.getElementById("root")?.style.setProperty("background", "transparent"); } + }, [windowType]); + useEffect(() => { // Load custom fonts on app initialization loadAllCustomFonts().catch((error) => { console.error("Failed to load custom fonts:", error); }); - }, [windowType]); + }, []); const content = (() => { switch (windowType) { diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 6644f42..ca14dfe 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -370,15 +370,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; }, [teardownMedia]); - const cancelCountdown = () => { - const activeRunId = countdownRunId.current; - countdownRunId.current += 1; - setCountdownActive(false); - void window.electronAPI.hideCountdownOverlay(activeRunId).catch((error) => { - console.warn("Failed to hide countdown overlay during cancel:", error); - }); - }; - const safeShowCountdownOverlay = async (value: number, runId: number) => { try { await window.electronAPI.showCountdownOverlay(value, runId); @@ -389,6 +380,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } }; + const cancelCountdown = () => { + const activeRunId = countdownRunId.current; + countdownRunId.current += 1; + setCountdownActive(false); + void safeHideCountdownOverlay(activeRunId); + }; + const safeSetCountdownOverlayValue = async (value: number, runId: number) => { try { await window.electronAPI.setCountdownOverlayValue(value, runId); @@ -436,6 +434,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } + let overlayHiddenBeforeStart = false; try { const values = [3, 2, 1]; const overlayShown = await safeShowCountdownOverlay(values[0], runId); @@ -464,9 +463,17 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } + setCountdownActive(false); + await safeHideCountdownOverlay(runId); + overlayHiddenBeforeStart = true; + + if (countdownRunId.current !== runId) { + return; + } + await startRecording(runId); } finally { - if (countdownRunId.current === runId) { + if (!overlayHiddenBeforeStart && countdownRunId.current === runId) { setCountdownActive(false); await safeHideCountdownOverlay(runId); } From f04c2b7c14d1062d9cd74cd88a0191709b2810e8 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Sun, 19 Apr 2026 02:49:17 -0700 Subject: [PATCH 107/152] fix(annotations): wrap CJK text at character boundaries in export renderer renderText split each line on whitespace, which works for Latin text but leaves CJK strings as a single unbreakable token because CJK scripts have no word-separating whitespace. Result: CJK annotation text overflows the clipped annotation box even though the editor's HTML preview wraps it correctly via CSS word-break: break-word. Replace the ad-hoc whitespace split with a tokenizeForWrap helper that emits each CJK character (Hiragana, Katakana, Hangul Syllables, CJK Unified Ideographs + Extension A, and CJK Compatibility Ideographs) as its own token, while keeping Latin words + whitespace intact. The existing width-measurement wrap loop then handles CJK per-character, matching the editor's behavior. Closes #449 --- src/lib/exporter/annotationRenderer.ts | 41 +++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/lib/exporter/annotationRenderer.ts b/src/lib/exporter/annotationRenderer.ts index b0c4948..0b895a0 100644 --- a/src/lib/exporter/annotationRenderer.ts +++ b/src/lib/exporter/annotationRenderer.ts @@ -10,6 +10,39 @@ import { let blurScratchCanvas: HTMLCanvasElement | null = null; let blurScratchCtx: CanvasRenderingContext2D | null = null; +// Matches a single code point in Hiragana, Katakana, CJK Unified Ideographs +// Extension A, CJK Unified Ideographs, Hangul Syllables, or CJK Compatibility +// Ideographs. Used to split CJK text at character boundaries during wrap, +// since CJK scripts have no word-separating whitespace. +const CJK_CHAR = + /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff]/u; + +function tokenizeForWrap(line: string): string[] { + // Split Latin text on whitespace (preserving the whitespace as its own token, + // matching the original behavior), and split CJK runs into individual + // characters so each one becomes a breakable unit. This mirrors the editor's + // CSS `word-break: break-word` handling for CJK content. + const tokens: string[] = []; + let buffer = ""; + const chars = Array.from(line); + const flushBuffer = () => { + if (buffer) { + tokens.push(...buffer.split(/(\s+)/).filter((s) => s.length > 0)); + buffer = ""; + } + }; + for (const ch of chars) { + if (CJK_CHAR.test(ch)) { + flushBuffer(); + tokens.push(ch); + } else { + buffer += ch; + } + } + flushBuffer(); + return tokens; +} + // SVG path data for each arrow direction const ARROW_PATHS: Record = { up: ["M 50 20 L 50 80", "M 50 20 L 35 35", "M 50 20 L 65 35"], @@ -249,13 +282,13 @@ function renderText( lines.push(""); continue; } - const words = rawLine.split(/(\s+)/); + const tokens = tokenizeForWrap(rawLine); let current = ""; - for (const word of words) { - const test = current + word; + for (const token of tokens) { + const test = current + token; if (current && ctx.measureText(test).width > availableWidth) { lines.push(current); - current = word.trimStart(); + current = token.trimStart(); } else { current = test; } From d6bf31cb3f6068e0660137888724dc536af7d38e Mon Sep 17 00:00:00 2001 From: ichi Date: Sun, 19 Apr 2026 20:44:07 +0900 Subject: [PATCH 108/152] feat(i18n): add Japanese locale and update translations for existing locales - Added Japanese (ja-JP) translations for common, editor, dialogs, launch, settings, shortcuts, and timeline. - Updated translations for existing locales (en, es, fr, ko-KR, tr, zh-CN, zh-TW) to include new keys for "showInFolder", "loadingVideo", "trim", and "speed". - Refactored VideoEditor and timeline Item components to utilize localized strings for various user interface elements and notifications. - Enhanced user experience by providing localized messages for project loading, exporting, and timeline actions. --- electron/i18n.ts | 25 ++- src/components/video-editor/VideoEditor.tsx | 46 +++-- src/components/video-editor/timeline/Item.tsx | 6 +- src/i18n/config.ts | 11 +- src/i18n/locales/en/common.json | 1 + src/i18n/locales/en/editor.json | 1 + src/i18n/locales/en/timeline.json | 2 + src/i18n/locales/es/common.json | 1 + src/i18n/locales/es/timeline.json | 2 + src/i18n/locales/fr/common.json | 1 + src/i18n/locales/fr/timeline.json | 2 + src/i18n/locales/ja-JP/common.json | 30 +++ src/i18n/locales/ja-JP/dialogs.json | 71 +++++++ src/i18n/locales/ja-JP/editor.json | 42 ++++ src/i18n/locales/ja-JP/launch.json | 43 ++++ src/i18n/locales/ja-JP/settings.json | 183 ++++++++++++++++++ src/i18n/locales/ja-JP/shortcuts.json | 37 ++++ src/i18n/locales/ja-JP/timeline.json | 55 ++++++ src/i18n/locales/ko-KR/common.json | 1 + src/i18n/locales/ko-KR/editor.json | 1 + src/i18n/locales/ko-KR/timeline.json | 2 + src/i18n/locales/tr/common.json | 1 + src/i18n/locales/tr/timeline.json | 2 + src/i18n/locales/zh-CN/common.json | 1 + src/i18n/locales/zh-CN/editor.json | 1 + src/i18n/locales/zh-CN/timeline.json | 2 + src/i18n/locales/zh-TW/common.json | 1 + src/i18n/locales/zh-TW/editor.json | 1 + src/i18n/locales/zh-TW/timeline.json | 2 + 29 files changed, 552 insertions(+), 22 deletions(-) create mode 100644 src/i18n/locales/ja-JP/common.json create mode 100644 src/i18n/locales/ja-JP/dialogs.json create mode 100644 src/i18n/locales/ja-JP/editor.json create mode 100644 src/i18n/locales/ja-JP/launch.json create mode 100644 src/i18n/locales/ja-JP/settings.json create mode 100644 src/i18n/locales/ja-JP/shortcuts.json create mode 100644 src/i18n/locales/ja-JP/timeline.json diff --git a/electron/i18n.ts b/electron/i18n.ts index 2dfb4d3..4222741 100644 --- a/electron/i18n.ts +++ b/electron/i18n.ts @@ -7,24 +7,45 @@ import commonEs from "../src/i18n/locales/es/common.json"; import dialogsEs from "../src/i18n/locales/es/dialogs.json"; import commonFr from "../src/i18n/locales/fr/common.json"; import dialogsFr from "../src/i18n/locales/fr/dialogs.json"; +import commonJa from "../src/i18n/locales/ja-JP/common.json"; +import dialogsJa from "../src/i18n/locales/ja-JP/dialogs.json"; +import commonKo from "../src/i18n/locales/ko-KR/common.json"; +import dialogsKo from "../src/i18n/locales/ko-KR/dialogs.json"; +import commonTr from "../src/i18n/locales/tr/common.json"; +import dialogsTr from "../src/i18n/locales/tr/dialogs.json"; import commonZh from "../src/i18n/locales/zh-CN/common.json"; import dialogsZh from "../src/i18n/locales/zh-CN/dialogs.json"; +import commonZhTw from "../src/i18n/locales/zh-TW/common.json"; +import dialogsZhTw from "../src/i18n/locales/zh-TW/dialogs.json"; -type Locale = "en" | "zh-CN" | "es" | "fr"; +type Locale = "en" | "zh-CN" | "zh-TW" | "es" | "fr" | "ja-JP" | "ko-KR" | "tr"; type Namespace = "common" | "dialogs"; type MessageMap = Record; const messages: Record> = { en: { common: commonEn, dialogs: dialogsEn }, "zh-CN": { common: commonZh, dialogs: dialogsZh }, + "zh-TW": { common: commonZhTw, dialogs: dialogsZhTw }, es: { common: commonEs, dialogs: dialogsEs }, fr: { common: commonFr, dialogs: dialogsFr }, + "ja-JP": { common: commonJa, dialogs: dialogsJa }, + "ko-KR": { common: commonKo, dialogs: dialogsKo }, + tr: { common: commonTr, dialogs: dialogsTr }, }; let currentLocale: Locale = "en"; export function setMainLocale(locale: string) { - if (locale === "en" || locale === "zh-CN" || locale === "es" || locale === "fr") { + if ( + locale === "en" || + locale === "zh-CN" || + locale === "zh-TW" || + locale === "es" || + locale === "fr" || + locale === "ja-JP" || + locale === "ko-KR" || + locale === "tr" + ) { currentLocale = locale; } } diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 6d21d13..7b558ce 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -153,10 +153,10 @@ export default function VideoEditor() { const nextSpeedIdRef = useRef(1); const { shortcuts, isMac } = useShortcuts(); + const { locale, setLocale, t: rawT } = useI18n(); const t = useScopedT("editor"); const ts = useScopedT("settings"); const availableLocales = getAvailableLocales(); - const { locale, setLocale } = useI18n(); const nextAnnotationIdRef = useRef(1); const nextAnnotationZIndexRef = useRef(1); @@ -361,7 +361,10 @@ export default function VideoEditor() { setLastSavedSnapshot( createProjectSnapshot( webcamSourcePath - ? { screenVideoPath: sourcePath, webcamVideoPath: webcamSourcePath } + ? { + screenVideoPath: sourcePath, + webcamVideoPath: webcamSourcePath, + } : { screenVideoPath: sourcePath }, INITIAL_EDITOR_STATE, ), @@ -547,18 +550,18 @@ export default function VideoEditor() { } if (!result.success) { - toast.error(result.message || "Failed to load project"); + toast.error(result.message || t("project.failedToLoad")); return; } const restored = await applyLoadedProject(result.project, result.path ?? null); if (!restored) { - toast.error("Invalid project file format"); + toast.error(t("project.invalidFormat")); return; } - toast.success(`Project loaded from ${result.path}`); - }, [applyLoadedProject]); + toast.success(t("project.loadedFrom", { path: result.path ?? "" })); + }, [applyLoadedProject, t]); useEffect(() => { const removeLoadListener = window.electronAPI.onMenuLoadProject(handleLoadProject); @@ -961,7 +964,11 @@ export default function VideoEditor() { pushState((prev) => ({ zoomRegions: prev.zoomRegions.map((region) => region.id === id - ? { ...region, zoomInDurationMs: zoomIn, zoomOutDurationMs: zoomOut } + ? { + ...region, + zoomInDurationMs: zoomIn, + zoomOutDurationMs: zoomOut, + } : region, ), })); @@ -1295,17 +1302,22 @@ export default function VideoEditor() { const handleExportSaved = useCallback( (formatLabel: "GIF" | "Video", filePath: string) => { setExportedFilePath(filePath); - toast.success(`${formatLabel} exported successfully`, { - description: filePath, - action: { - label: "Show in Folder", - onClick: () => { - void handleShowExportedFile(filePath); + toast.success( + t("export.exportedSuccessfully", { + format: formatLabel, + }), + { + description: filePath, + action: { + label: rawT("common.actions.showInFolder"), + onClick: () => { + void handleShowExportedFile(filePath); + }, }, }, - }); + ); }, - [handleShowExportedFile], + [handleShowExportedFile, t, rawT], ); const handleSaveUnsavedExport = useCallback(async () => { @@ -1679,7 +1691,7 @@ export default function VideoEditor() { if (loading) { return (
-
Loading video...
+
{t("loadingVideo")}
); } @@ -1693,7 +1705,7 @@ export default function VideoEditor() { onClick={handleLoadProject} className="px-3 py-1.5 rounded-md bg-[#34B27B] text-white text-sm hover:bg-[#34B27B]/90" > - Load Project File + {ts("project.load")}
diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index d89de94..aeca711 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -2,6 +2,7 @@ import type { Span } from "dnd-timeline"; import { useItem, useTimelineContext } from "dnd-timeline"; import { Gauge, MessageSquare, Scissors, ZoomIn } from "lucide-react"; import { useMemo } from "react"; +import { useScopedT } from "@/contexts/I18nContext"; import { cn } from "@/lib/utils"; import { DEFAULT_ZOOM_IN_MS, @@ -59,6 +60,7 @@ export default function Item({ children, onZoomDurationChange, }: ItemProps) { + const t = useScopedT("timeline"); const { pixelsToValue } = useTimelineContext(); const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, @@ -251,14 +253,14 @@ export default function Item({ <> - Trim + {t("labels.trim")} ) : isSpeed ? ( <> - {speedValue !== undefined ? `${speedValue}×` : "Speed"} + {speedValue !== undefined ? `${speedValue}×` : t("labels.speed")} ) : ( diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 03761c8..788a315 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,5 +1,14 @@ export const DEFAULT_LOCALE = "en" as const; -export const SUPPORTED_LOCALES = ["en", "zh-CN", "zh-TW", "es", "fr", "tr", "ko-KR"] as const; +export const SUPPORTED_LOCALES = [ + "en", + "zh-CN", + "zh-TW", + "es", + "fr", + "tr", + "ko-KR", + "ja-JP", +] as const; export const I18N_NAMESPACES = [ "common", "dialogs", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index fdc65e6..cdefe84 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -9,6 +9,7 @@ "open": "Open", "upload": "Upload", "export": "Export", + "showInFolder": "Show in Folder", "file": "File", "edit": "Edit", "view": "View", diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index ea2ceaa..cbff4a9 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -5,6 +5,7 @@ "cancel": "Cancel", "confirm": "Confirm" }, + "loadingVideo": "Loading video...", "errors": { "noVideoLoaded": "No video loaded", "videoNotReady": "Video not ready", diff --git a/src/i18n/locales/en/timeline.json b/src/i18n/locales/en/timeline.json index b4d5bd8..389184b 100644 --- a/src/i18n/locales/en/timeline.json +++ b/src/i18n/locales/en/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "Pan", "zoom": "Zoom", + "trim": "Trim", + "speed": "Speed", "zoomItem": "Zoom {{index}}", "trimItem": "Trim {{index}}", "speedItem": "Speed {{index}}", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index a6f61e7..13cccb0 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -9,6 +9,7 @@ "open": "Abrir", "upload": "Subir", "export": "Exportar", + "showInFolder": "Mostrar en carpeta", "file": "Archivo", "edit": "Editar", "view": "Vista", diff --git a/src/i18n/locales/es/timeline.json b/src/i18n/locales/es/timeline.json index 12a83b0..1a1abe1 100644 --- a/src/i18n/locales/es/timeline.json +++ b/src/i18n/locales/es/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "Desplazar", "zoom": "Zoom", + "trim": "Recortar", + "speed": "Velocidad", "zoomItem": "Zoom {{index}}", "trimItem": "Recorte {{index}}", "speedItem": "Velocidad {{index}}", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 7eb7f83..5d09214 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -9,6 +9,7 @@ "open": "Ouvrir", "upload": "Téléverser", "export": "Exporter", + "showInFolder": "Afficher dans le dossier", "file": "Fichier", "edit": "Éditer", "view": "Affichage", diff --git a/src/i18n/locales/fr/timeline.json b/src/i18n/locales/fr/timeline.json index 5985ea6..b4c6e16 100644 --- a/src/i18n/locales/fr/timeline.json +++ b/src/i18n/locales/fr/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "Panoramique", "zoom": "Zoom", + "trim": "Couper", + "speed": "Vitesse", "zoomItem": "Zoom {{index}}", "trimItem": "Coupe {{index}}", "speedItem": "Vitesse {{index}}", diff --git a/src/i18n/locales/ja-JP/common.json b/src/i18n/locales/ja-JP/common.json new file mode 100644 index 0000000..ee2205a --- /dev/null +++ b/src/i18n/locales/ja-JP/common.json @@ -0,0 +1,30 @@ +{ + "actions": { + "cancel": "キャンセル", + "save": "保存", + "delete": "削除", + "close": "閉じる", + "share": "共有", + "done": "完了", + "open": "開く", + "upload": "アップロード", + "export": "エクスポート", + "showInFolder": "フォルダに表示", + "file": "ファイル", + "edit": "編集", + "view": "表示", + "window": "ウィンドウ", + "quit": "終了", + "stopRecording": "録画停止" + }, + "playback": { + "play": "再生", + "pause": "一時停止", + "fullscreen": "全画面表示", + "exitFullscreen": "全画面表示を終了" + }, + "locale": { + "name": "日本語", + "short": "JA" + } +} diff --git a/src/i18n/locales/ja-JP/dialogs.json b/src/i18n/locales/ja-JP/dialogs.json new file mode 100644 index 0000000..3c3fce5 --- /dev/null +++ b/src/i18n/locales/ja-JP/dialogs.json @@ -0,0 +1,71 @@ +{ + "export": { + "complete": "エクスポート完了", + "yourFormatReady": "あなたの{{format}}が準備できました", + "showInFolder": "フォルダで表示", + "finalizingVideo": "ビデオのエクスポートを最終処理中...", + "compilingGifProgress": "GIFをコンパイル中... {{progress}}%", + "compilingGifWait": "GIFをコンパイル中... しばらくお待ちください", + "takeMoment": "少々お待ちください...", + "failed": "エクスポートに失敗しました", + "tryAgain": "もう一度お試しください", + "finalizingVideoTitle": "ビデオの最終処理", + "compilingGif": "GIFをコンパイル中", + "exportingFormat": "{{format}}をエクスポート中", + "compiling": "コンパイル中", + "renderingFrames": "フレームをレンダリング中", + "processing": "処理中...", + "finalizing": "最終処理中...", + "compilingStatus": "コンパイル中...", + "status": "ステータス", + "format": "フォーマット", + "frames": "フレーム", + "cancelExport": "エクスポートをキャンセル", + "savedSuccessfully": "{{format}}を正常に保存しました!" + }, + "tutorial": { + "triggerLabel": "トリミングの仕組み", + "title": "トリミングの仕組み", + "description": "動画の不要な部分を削除する方法について解説します。", + "explanationBefore": "トリムツールは、動画から", + "remove": "「削除したい部分」", + "explanationMiddle": "を指定することで機能します。エクスポート時には、赤い枠で", + "covered": "囲まれた部分", + "explanationAfter": "がすべてカットされます。", + "visualExample": "視覚的な例", + "removed": "削除", + "kept": "保持", + "part1": "パート 1", + "part2": "パート 2", + "part3": "パート 3", + "finalVideo": "完成動画", + "step1Title": "1. 削除範囲を追加", + "step1DescriptionBefore": "キーボードの", + "step1DescriptionAfter": "、またはハサミのアイコンをクリックして、削除したい範囲を指定します。", + + "step2Title": "2. 範囲を調整", + "step2Description": "赤い領域の両端をドラッグして、削除したい範囲を正確に調整します。" + }, + "unsavedChanges": { + "title": "未保存の変更", + "message": "未保存の変更があります。", + "detail": "閉じる前にプロジェクトを保存しますか?", + "saveAndClose": "保存して閉じる", + "discardAndClose": "破棄して閉じる", + "loadProject": "プロジェクトを読み込む…", + "saveProject": "プロジェクトを保存…", + "saveProjectAs": "プロジェクトを名前を付けて保存…" + }, + "fileDialogs": { + "saveGif": "エクスポートしたGIFを保存", + "saveVideo": "エクスポートしたビデオを保存", + "selectVideo": "ビデオファイルを選択", + "saveProject": "OpenScreen プロジェクトを保存", + "openProject": "OpenScreen プロジェクトを開く", + "gifImage": "GIF 画像", + "mp4Video": "MP4 ビデオ", + "videoFiles": "ビデオファイル", + "openscreenProject": "OpenScreen プロジェクト", + "allFiles": "すべてのファイル" + } +} diff --git a/src/i18n/locales/ja-JP/editor.json b/src/i18n/locales/ja-JP/editor.json new file mode 100644 index 0000000..1501230 --- /dev/null +++ b/src/i18n/locales/ja-JP/editor.json @@ -0,0 +1,42 @@ +{ + "newRecording": { + "title": "レコーダーに戻る", + "description": "現在の作業内容が保存されました。", + "cancel": "キャンセル", + "confirm": "確認" + }, + "loadingVideo": "ビデオを読み込み中...", + "errors": { + "noVideoLoaded": "ビデオが読み込まれていません", + "videoNotReady": "ビデオが準備できていません", + "unableToDetermineSourcePath": "ソースビデオのパスを特定できません", + "failedToSaveGif": "GIFの保存に失敗しました", + "gifExportFailed": "GIFのエクスポートに失敗しました", + "failedToSaveVideo": "ビデオの保存に失敗しました", + "exportFailed": "エクスポートに失敗しました", + "exportFailedWithError": "エクスポートに失敗しました: {{error}}", + "failedToSaveExport": "エクスポートの保存に失敗しました", + "failedToSaveExportedVideo": "エクスポートしたビデオの保存に失敗しました", + "failedToRevealInFolder": "フォルダの表示に失敗しました: {{error}}" + }, + "export": { + "canceled": "エクスポートがキャンセルされました", + "exportedSuccessfully": "{{format}}を正常にエクスポートしました" + }, + "project": { + "saveCanceled": "プロジェクトの保存がキャンセルされました", + "failedToSave": "プロジェクトの保存に失敗しました", + "savedTo": "プロジェクトを保存しました: {{path}}", + "failedToLoad": "プロジェクトの読み込みに失敗しました", + "invalidFormat": "無効なプロジェクトファイル形式です", + "loadedFrom": "プロジェクトを読み込みました: {{path}}" + }, + "recording": { + "failedCameraAccess": "カメラのアクセス要求に失敗しました。", + "cameraBlocked": "カメラのアクセスがブロックされています。システム設定で有効にして、ウェブカメラを使用してください。", + "systemAudioUnavailable": "システムオーディオが利用できません。システムオーディオなしで録画します。", + "microphoneDenied": "マイクのアクセスが拒否されました。オーディオなしで録画を続行します。", + "cameraDenied": "カメラのアクセスが拒否されました。ウェブカメラなしで録画を続行します。", + "permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。" + } +} diff --git a/src/i18n/locales/ja-JP/launch.json b/src/i18n/locales/ja-JP/launch.json new file mode 100644 index 0000000..4504b00 --- /dev/null +++ b/src/i18n/locales/ja-JP/launch.json @@ -0,0 +1,43 @@ +{ + "tooltips": { + "hideHUD": "HUDを隠す", + "closeApp": "アプリを閉じる", + "restartRecording": "録画を再開", + "cancelRecording": "録画をキャンセル", + "pauseRecording": "録画を一時停止", + "resumeRecording": "録画を再開", + "openVideoFile": "ビデオファイルを開く", + "openProject": "プロジェクトを開く" + }, + "audio": { + "enableSystemAudio": "システムオーディオを有効にする", + "disableSystemAudio": "システムオーディオを無効にする", + "enableMicrophone": "マイクを有効にする", + "disableMicrophone": "マイクを無効にする", + "defaultMicrophone": "デフォルトのマイク" + }, + "webcam": { + "enableWebcam": "ウェブカメラを有効にする", + "disableWebcam": "ウェブカメラを無効にする", + "defaultCamera": "デフォルトのカメラ", + "searching": "検索中...", + "noneFound": "カメラが見つかりません", + "unavailable": "カメラが利用できません" + }, + "sourceSelector": { + "loading": "ソースを読み込み中...", + "screens": "画面 ({{count}})", + "windows": "ウィンドウ ({{count}})", + "defaultSourceName": "画面" + }, + "recording": { + "selectSource": "録画するソースを選択してください" + }, + "language": "言語", + "systemLanguagePrompt": { + "title": "システム言語を使用しますか?", + "description": "システム言語として{{language}}が検出されました。OpenScreenを{{language}}に切り替えますか?", + "switch": "{{language}}に切り替え", + "keepDefault": "現在の言語を保持" + } +} diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json new file mode 100644 index 0000000..9cad3ef --- /dev/null +++ b/src/i18n/locales/ja-JP/settings.json @@ -0,0 +1,183 @@ +{ + "zoom": { + "level": "ズーム倍率", + "selectRegion": "ズーム範囲を選択して調整", + "deleteZoom": "ズームを削除", + "focusMode": { + "title": "フォーカスモード", + "manual": "手動", + "auto": "自動", + "autoDescription": "カメラが録画中のカーソル位置に追従します" + }, + "speed": { + "title": "ズーム速度", + "instant": "即時", + "fast": "高速", + "smooth": "滑らか", + "lazy": "遅延" + } + }, + "speed": { + "playbackSpeed": "再生速度", + "selectRegion": "速度範囲を選択して調整", + "deleteRegion": "速度範囲を削除", + "customPlaybackSpeed": "カスタム再生速度", + "maxSpeedError": "速度は16×を超えることはできません" + }, + "trim": { + "deleteRegion": "トリム範囲を削除" + }, + "layout": { + "title": "レイアウト", + "preset": "プリセット", + "selectPreset": "プリセットを選択", + "pictureInPicture": "ピクチャーインピクチャー", + "verticalStack": "縦積み", + "dualFrame": "デュアルフレーム", + "webcamShape": "カメラの形状", + "webcamSize": "カメラのサイズ" + }, + "effects": { + "title": "ビデオ効果", + "blurBg": "背景をぼかす", + "motionBlur": "モーションブラー", + "off": "オフ", + "shadow": "影", + "roundness": "丸み", + "padding": "余白" + }, + "background": { + "title": "背景", + "image": "画像", + "color": "色", + "gradient": "グラデーション", + "uploadCustom": "カスタムをアップロード", + "gradientLabel": "グラデーション {{index}}" + }, + "crop": { + "title": "クロップ", + "cropVideo": "ビデオをクロップ", + "dragInstruction": "各辺をドラッグしてクロップ範囲を調整", + "ratio": "比率", + "free": "自由", + "done": "完了", + "lockAspectRatio": "アスペクト比を固定", + "unlockAspectRatio": "アスペクト比の固定を解除" + }, + "exportFormat": { + "mp4": "MP4", + "gif": "GIF", + "mp4Video": "MP4 ビデオ", + "mp4Description": "高品質のビデオファイル", + "gifAnimation": "GIF アニメーション", + "gifDescription": "共有用のアニメーション画像" + }, + "exportQuality": { + "title": "エクスポート品質", + "low": "低画質", + "medium": "中画質", + "high": "高画質" + }, + "gifSettings": { + "frameRate": "GIF フレームレート", + "size": "GIF サイズ", + "loop": "GIF をループする" + }, + "project": { + "save": "プロジェクトを保存", + "load": "プロジェクトを読み込む" + }, + "export": { + "videoButton": "ビデオをエクスポート", + "gifButton": "GIF をエクスポート", + "chooseSaveLocation": "保存場所を選択" + }, + "links": { + "reportBug": "バグを報告", + "starOnGithub": "GitHub でスターを付ける" + }, + "imageUpload": { + "invalidFileType": "無効なファイル形式", + "jpgOnly": "JPG または JPEG 画像ファイルをアップロードしてください。", + "uploadSuccess": "カスタム画像が正常にアップロードされました!", + "failedToUpload": "画像のアップロードに失敗しました", + "errorReading": "ファイルの読み取り中にエラーが発生しました。" + }, + "annotation": { + "title": "注釈設定", + "active": "アクティブ", + "typeText": "テキスト", + "typeImage": "画像", + "typeArrow": "矢印", + "typeBlur": "ぼかし", + "textContent": "テキスト内容", + "textPlaceholder": "テキストを入力してください...", + "fontStyle": "フォントスタイル", + "selectStyle": "スタイルを選択", + "size": "サイズ", + "customFonts": "カスタムフォント", + "textColor": "文字色", + "background": "背景", + "none": "なし", + "color": "色", + "clearBackground": "背景をクリア", + "uploadImage": "画像をアップロード", + "supportedFormats": "サポートされている形式: JPG, PNG, GIF, WebP", + "arrowDirection": "矢印の方向", + "strokeWidth": "線の太さ: {{width}}px", + "arrowColor": "矢印の色", + "blurType": "ぼかしの種類", + "blurTypeBlur": "ぼかし", + "blurTypeMosaic": "モザイクぼかし", + "blurColor": "ぼかしの色", + "blurColorWhite": "白", + "blurColorBlack": "黒", + "blurShape": "ぼかしの形状", + "blurIntensity": "ぼかしの強さ", + "mosaicBlockSize": "モザイクブロックのサイズ", + "blurShapeRectangle": "長方形", + "blurShapeOval": "楕円", + "blurShapeFreehand": "自由形状", + "deleteAnnotation": "注釈を削除", + "shortcutsAndTips": "ショートカットとヒント", + "tipMovePlayhead": "重なっている注釈セクションに再生ヘッドを移動し、項目を選択します。", + "tipTabCycle": "Tabキーを使用して重なっている項目を順に切り替えます。", + "tipShiftTabCycle": "Shift+Tabキーを使用して逆順に切り替えます。", + "invalidImageType": "無効なファイル形式", + "imageFormatsOnly": "JPG、PNG、GIF、またはWebP画像ファイルをアップロードしてください。", + "imageUploadSuccess": "画像が正常にアップロードされました!", + "failedImageUpload": "画像のアップロードに失敗しました" + }, + "fontStyles": { + "classic": "クラシック", + "editor": "エディター", + "strong": "ストロング", + "typewriter": "タイプライター", + "deco": "デコ", + "simple": "シンプル", + "modern": "モダン", + "clean": "クリーン" + }, + "customFont": { + "dialogTitle": "Googleフォントを追加", + "urlLabel": "GoogleフォントのインポートURL", + "urlPlaceholder": "https://fonts.googleapis.com/css2?family=Roboto&display=swap", + "urlHelp": "Googleフォントから取得: フォントを選択 → 「フォントを取得」をクリック → @import URLをコピー", + "nameLabel": "表示名", + "namePlaceholder": "マイカスタムフォント", + "nameHelp": "フォントセレクターに表示される名前です", + "addButton": "フォントを追加", + "addingButton": "追加中...", + "errorEmptyUrl": "GoogleフォントのインポートURLを入力してください", + "errorInvalidUrl": "有効なGoogleフォントURLを入力してください", + "errorEmptyName": "フォント名を入力してください", + "errorExtractFailed": "URLからフォントファミリーを抽出できませんでした", + "successMessage": "フォント \"{{fontName}}\" が正常に追加されました", + "failedToAdd": "フォントの追加に失敗しました", + "errorTimeout": "フォントの読み込みに時間がかかりすぎました。URLを確認して再試行してください。", + "errorLoadFailed": "フォントを読み込めませんでした。GoogleフォントのURLが正しいことを確認してください。" + }, + "language": { + "title": "言語" + } +} diff --git a/src/i18n/locales/ja-JP/shortcuts.json b/src/i18n/locales/ja-JP/shortcuts.json new file mode 100644 index 0000000..d4b4a90 --- /dev/null +++ b/src/i18n/locales/ja-JP/shortcuts.json @@ -0,0 +1,37 @@ +{ + "title": "キーボードショートカット", + "customize": "カスタマイズ", + "configurable": "設定可能", + "fixed": "固定", + "pressKey": "キーを押してください…", + "clickToChange": "クリックして変更", + "pressEscToCancel": "Escキーを押してキャンセル", + "helpText": "ショートカットをクリックして新しいキーの組み合わせを押します。Escキーを押してキャンセルします。", + "resetToDefaults": "デフォルトにリセット", + "alreadyUsedBy": "すでに {{action}} に使用されています", + "swap": "入れ替え", + "reservedShortcut": "このショートカットは \"{{label}}\" に予約されており、再割り当てできません。", + "savedToast": "キーボードショートカットが保存されました", + "resetToast": "デフォルトのショートカットにリセット — 保存をクリックして適用", + "actions": { + "addZoom": "ズームを追加", + "addTrim": "トリムを追加", + "addSpeed": "速度を追加", + "addAnnotation": "注釈を追加", + "addBlur": "ぼかしを追加", + "addKeyframe": "キーフレームを追加", + "deleteSelected": "選択を削除", + "playPause": "再生 / 一時停止" + }, + "fixedActions": { + "undo": "元に戻す", + "redo": "やり直す", + "cycleAnnotationsForward": "注釈を順に切り替え", + "cycleAnnotationsBackward": "注釈を逆順に切り替え", + "deleteSelectedAlt": "選択を削除 (alt)", + "panTimeline": "タイムラインをパン", + "zoomTimeline": "タイムラインをズーム", + "frameBack": "フレームを戻す", + "frameForward": "フレームを進める" + } +} diff --git a/src/i18n/locales/ja-JP/timeline.json b/src/i18n/locales/ja-JP/timeline.json new file mode 100644 index 0000000..e0507f6 --- /dev/null +++ b/src/i18n/locales/ja-JP/timeline.json @@ -0,0 +1,55 @@ +{ + "buttons": { + "addZoom": "ズームを追加 (Z)", + "suggestZooms": "カーソル位置からズームを提案", + "addTrim": "トリムを追加 (T)", + "addAnnotation": "注釈を追加 (A)", + "addBlur": "ぼかしを追加 (B)", + "addSpeed": "速度を追加 (S)" + }, + "hints": { + "pressZoom": "Zキーを押してズームを追加", + "pressTrim": "Tキーを押してトリムを追加", + "pressAnnotation": "Aキーを押して注釈を追加", + "pressBlur": "Bキーを押してぼかしを追加", + "pressSpeed": "Sキーを押して速度を追加" + }, + "labels": { + "pan": "移動", + "zoom": "ズーム", + "trim": "トリム", + "speed": "速度", + "zoomItem": "ズーム {{index}}", + "trimItem": "トリム {{index}}", + "speedItem": "速度 {{index}}", + "annotationItem": "注釈", + "blurItem": "ぼかし {{index}}", + "imageItem": "画像", + "emptyText": "空のテキスト" + }, + "emptyState": { + "noVideo": "ビデオが読み込まれていません", + "dragAndDrop": "ビデオをドラッグアンドドロップして編集を開始してください" + }, + "errors": { + "cannotPlaceZoom": "ここにズームを配置できません", + "zoomExistsAtLocation": "この場所にはすでにズームが存在するか、十分なスペースがありません。", + "zoomSuggestionUnavailable": "ズームの自動提案機能が利用できません", + "noCursorTelemetry": "カーソルの動きが記録されていません", + "noCursorTelemetryDescription": "まず画面収録を行い、カーソルに基づく提案を生成してください。", + "noUsableTelemetry": "使用可能なカーソルの動きデータがありません", + "noUsableTelemetryDescription": "録画には十分なカーソルの動きデータが含まれていません。", + "noDwellMoments": "カーソルが静止したポイントが見つかりません", + "noDwellMomentsDescription": "強調したい操作の際に、カーソルを一時停止させて録画してみてください。", + "noAutoZoomSlots": "自動ズームを適用できる箇所がありません", + "noAutoZoomSlotsDescription": "検出された滞留ポイントが既存のズーム領域と重なっています。", + "cannotPlaceTrim": "ここに切り取りを配置できません", + "trimExistsAtLocation": "この場所にはすでに切り取りが存在するか、十分なスペースがありません。", + "cannotPlaceSpeed": "ここに速度を配置できません", + "speedExistsAtLocation": "この場所にはすでに速度が存在するか、十分なスペースがありません。" + }, + "success": { + "addedZoomSuggestions": "カーソルに基づくズーム提案を {{count}} 件追加しました", + "addedZoomSuggestionsPlural": "カーソルに基づくズーム提案を {{count}} 件追加しました" + } +} diff --git a/src/i18n/locales/ko-KR/common.json b/src/i18n/locales/ko-KR/common.json index b83cb44..5d90118 100644 --- a/src/i18n/locales/ko-KR/common.json +++ b/src/i18n/locales/ko-KR/common.json @@ -9,6 +9,7 @@ "open": "열기", "upload": "업로드", "export": "내보내기", + "showInFolder": "폴더에 표시", "file": "파일", "edit": "편집", "view": "보기", diff --git a/src/i18n/locales/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json index 4db7d1f..5d02772 100644 --- a/src/i18n/locales/ko-KR/editor.json +++ b/src/i18n/locales/ko-KR/editor.json @@ -5,6 +5,7 @@ "cancel": "취소", "confirm": "확인" }, + "loadingVideo": "비디오 로드 중...", "errors": { "noVideoLoaded": "불러온 비디오가 없습니다", "videoNotReady": "비디오가 준비되지 않았습니다", diff --git a/src/i18n/locales/ko-KR/timeline.json b/src/i18n/locales/ko-KR/timeline.json index 167c26f..7009e68 100644 --- a/src/i18n/locales/ko-KR/timeline.json +++ b/src/i18n/locales/ko-KR/timeline.json @@ -15,6 +15,8 @@ "labels": { "pan": "이동", "zoom": "줌", + "trim": "트림", + "speed": "속도", "zoomItem": "줌 {{index}}", "trimItem": "트림 {{index}}", "speedItem": "속도 {{index}}", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 3ec132c..13d006b 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -9,6 +9,7 @@ "open": "Aç", "upload": "Yükle", "export": "Dışa Aktar", + "showInFolder": "Klasörde Göster", "file": "Dosya", "edit": "Düzenle", "view": "Görünüm", diff --git a/src/i18n/locales/tr/timeline.json b/src/i18n/locales/tr/timeline.json index 294640b..2632f7a 100644 --- a/src/i18n/locales/tr/timeline.json +++ b/src/i18n/locales/tr/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "Kaydır", "zoom": "Yakınlaştır", + "trim": "Kırp", + "speed": "Hız", "zoomItem": "Yakınlaştırma {{index}}", "trimItem": "Kırpma {{index}}", "speedItem": "Hız {{index}}", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index d8bff69..2291fa6 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -9,6 +9,7 @@ "open": "打开", "upload": "上传", "export": "导出", + "showInFolder": "在文件夹中显示", "file": "文件", "edit": "编辑", "view": "视图", diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 44abab9..6d2f745 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -5,6 +5,7 @@ "cancel": "取消", "confirm": "确认" }, + "loadingVideo": "正在加载视频...", "errors": { "noVideoLoaded": "未加载视频", "videoNotReady": "视频未就绪", diff --git a/src/i18n/locales/zh-CN/timeline.json b/src/i18n/locales/zh-CN/timeline.json index 7841dcb..783fef6 100644 --- a/src/i18n/locales/zh-CN/timeline.json +++ b/src/i18n/locales/zh-CN/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "平移", "zoom": "缩放", + "trim": "剪辑", + "speed": "速度", "zoomItem": "缩放 {{index}}", "trimItem": "剪辑 {{index}}", "speedItem": "速度 {{index}}", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 971d9ab..1ddab48 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -9,6 +9,7 @@ "open": "開啟", "upload": "上傳", "export": "匯出", + "showInFolder": "在資料夾中顯示", "file": "檔案", "edit": "編輯", "view": "檢視", diff --git a/src/i18n/locales/zh-TW/editor.json b/src/i18n/locales/zh-TW/editor.json index 73a3f4e..0ef9155 100644 --- a/src/i18n/locales/zh-TW/editor.json +++ b/src/i18n/locales/zh-TW/editor.json @@ -5,6 +5,7 @@ "cancel": "取消", "confirm": "確認" }, + "loadingVideo": "正在載入影片...", "errors": { "noVideoLoaded": "未載入影片", "videoNotReady": "影片未就緒", diff --git a/src/i18n/locales/zh-TW/timeline.json b/src/i18n/locales/zh-TW/timeline.json index 52457d6..cd47b74 100644 --- a/src/i18n/locales/zh-TW/timeline.json +++ b/src/i18n/locales/zh-TW/timeline.json @@ -17,6 +17,8 @@ "labels": { "pan": "平移", "zoom": "縮放", + "trim": "剪輯", + "speed": "速度", "zoomItem": "縮放 {{index}}", "trimItem": "剪輯 {{index}}", "speedItem": "速度 {{index}}", From dd622f83c1e410e987587cab7568ac6bc06b5f74 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Sun, 19 Apr 2026 10:05:48 -0700 Subject: [PATCH 109/152] fix(annotations): use Unicode script properties for CJK detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on #471 from @coderabbitai. The BMP-only codepoint ranges missed two classes of characters: - Non-BMP Han extensions (CJK Unified Ideographs Extension B, C, D, E, F) such as 𠀀. A long string of Extension-B characters would still be tokenized as a single unbreakable unit and overflow the box. - Halfwidth Katakana (U+FF65-U+FF9F) such as カ. Same failure mode. Switch to Unicode script property escapes (\\p{Script=Han}, \\p{Script=Hiragana}, \\p{Script=Katakana}, \\p{Script=Hangul}) which cover these cases without enumerating ranges. tsconfig target is ES2020; property escapes require ES2018+ so this is safe. Verified coverage: 漢 あ ア 가 𠀀 カ all match; A and digits do not. --- src/lib/exporter/annotationRenderer.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/exporter/annotationRenderer.ts b/src/lib/exporter/annotationRenderer.ts index 0b895a0..c0d5657 100644 --- a/src/lib/exporter/annotationRenderer.ts +++ b/src/lib/exporter/annotationRenderer.ts @@ -10,12 +10,12 @@ import { let blurScratchCanvas: HTMLCanvasElement | null = null; let blurScratchCtx: CanvasRenderingContext2D | null = null; -// Matches a single code point in Hiragana, Katakana, CJK Unified Ideographs -// Extension A, CJK Unified Ideographs, Hangul Syllables, or CJK Compatibility -// Ideographs. Used to split CJK text at character boundaries during wrap, -// since CJK scripts have no word-separating whitespace. -const CJK_CHAR = - /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff]/u; +// Matches a single code point whose script is Han (including non-BMP +// Extension A-F), Hiragana, Katakana (including halfwidth forms), or +// Hangul. Used to split CJK text at character boundaries during wrap, +// since CJK scripts have no word-separating whitespace. Unicode script +// property escapes require ES2018+; tsconfig target is ES2020. +const CJK_CHAR = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u; function tokenizeForWrap(line: string): string[] { // Split Latin text on whitespace (preserving the whitespace as its own token, From 0bb14f3a334903265e3023166a0b8b2a7c3e5488 Mon Sep 17 00:00:00 2001 From: Fabien Laurence <86679051+FabLrc@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:07:17 +0200 Subject: [PATCH 110/152] Update src/components/launch/LaunchWindow.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/components/launch/LaunchWindow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 2914584..05e78a4 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -314,7 +314,7 @@ export function LaunchWindow() { }; return ( -
+
{systemLocaleSuggestion && (
Date: Mon, 20 Apr 2026 23:11:58 -0400 Subject: [PATCH 111/152] fix: add webm inflated duration and fix --- src/lib/exporter/streamingDecoder.ts | 12 ++++++++---- tests/fixtures/sample-inflated-duration.webm | Bin 0 -> 1252 bytes 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/sample-inflated-duration.webm diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 24b9844..c9a9597 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -246,14 +246,13 @@ export class StreamingVideoDecoder { const hintedDurationSec = Math.max(containerDurationSec, streamDurationSec, 0); const scanEndSec = hintedDurationSec > 0 ? hintedDurationSec + 0.5 : SCAN_UNBOUNDED_FALLBACK_SEC; - let maxPacketEndUs = 0; + let maxPacketTimestampUs = 0; const scanReader = this.demuxer.read("video", 0, scanEndSec).getReader(); try { while (true) { const { done, value } = await scanReader.read(); if (done || !value) break; - const endUs = value.timestamp + (value.duration ?? 0); - if (endUs > maxPacketEndUs) maxPacketEndUs = endUs; + if (value.timestamp > maxPacketTimestampUs) maxPacketTimestampUs = value.timestamp; } } finally { try { @@ -262,7 +261,12 @@ export class StreamingVideoDecoder { /* already closed */ } } - const scannedDuration = maxPacketEndUs / 1_000_000; + // Use last frame's timestamp + one frame duration. The `duration` field on the last + // packet in a WebM container is frequently inflated by the demuxer to match the + // container's declared end, causing validateDuration to see no divergence and + // propagate a duration that the decoder can never actually reach. + const typicalFrameDurationUs = Math.ceil(1_000_000 / frameRate); + const scannedDuration = (maxPacketTimestampUs + typicalFrameDurationUs) / 1_000_000; const validatedDuration = validateDuration(mediaInfo.duration, scannedDuration); this.metadata = { diff --git a/tests/fixtures/sample-inflated-duration.webm b/tests/fixtures/sample-inflated-duration.webm new file mode 100644 index 0000000000000000000000000000000000000000..6322840dba15e3d790d34e6e656c054f7e772bbf GIT binary patch literal 1252 zcmcK4O=uHA6ae72Nz_6Q{t+!wXt2dY#gJ55tf$E)QKP1MfTH5>`Vn_oy8H?!-(FI>vv7l ziE6aXoxdPQZ=A(Z`D}}OPH;_!xTb1Y?<&fNA>D(o)jDu;wcP*Ml&%WR3y;DZdEyjD zkM;)p+ggl!jb@YSxCMo_YY8%!SnWY+{$*b6jVw^kKcCpO-0&)w37G3sw|7`O zZT4?zqLc`m+;{T<84b>!Kr|G_4qe65eU7yBf0 z!2U^fYX-S)H*|(gMFCg6YNFifPF=EOBwlk!d${0 tW-el%XYNJ5p@z8u`w`}V{krO;yynV1GgiFugu?Tre4l*}ak)jR{R8OYZ`=R? literal 0 HcmV?d00001 From adc610544cb55e853823abca07fe5fc6b5ebffee Mon Sep 17 00:00:00 2001 From: shaun0927 <70629228+shaun0927@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:07:19 +0900 Subject: [PATCH 112/152] docs: document cursor telemetry buffer API and surface drop events Add JSDoc to every public export in cursorTelemetryBuffer so the module meets the 80% docstring-coverage threshold, and make two silent-drop paths observable: - endSession() now returns the number of pending batches evicted by the maxPendingBatches cap and emits console.warn when any are dropped. - prependBatch() defensively trims and warns if an unusual retry pattern would push the queue past the cap (normal retry after takeNextBatch() stays a no-op). Tests cover both drop paths. --- src/lib/cursorTelemetryBuffer.test.ts | 52 +++++++++++++- src/lib/cursorTelemetryBuffer.ts | 100 +++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 4 deletions(-) diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts index 5ffbc7a..309df7e 100644 --- a/src/lib/cursorTelemetryBuffer.test.ts +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { type CursorTelemetryPoint, createCursorTelemetryBuffer } from "./cursorTelemetryBuffer"; function sample(tag: number): CursorTelemetryPoint { @@ -140,6 +140,56 @@ describe("createCursorTelemetryBuffer", () => { expect(buf.pendingCount).toBe(0); }); + it("endSession() returns the number of dropped batches and warns when the cap is exceeded", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10, maxPendingBatches: 2 }); + const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); + + for (let round = 1; round <= 2; round++) { + buf.startSession(); + buf.push(sample(round)); + expect(buf.endSession()).toBe(0); + } + expect(warn).not.toHaveBeenCalled(); + + buf.startSession(); + buf.push(sample(3)); + const dropped = buf.endSession(); + expect(dropped).toBe(1); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]?.[0]).toMatch(/dropped 1 pending batch/); + expect(buf.pendingCount).toBe(2); + + warn.mockRestore(); + }); + + it("prependBatch() defensively trims and warns when it would exceed the cap", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10, maxPendingBatches: 2 }); + const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); + + // Fill the queue to the cap without dropping anything. + for (let round = 1; round <= 2; round++) { + buf.startSession(); + buf.push(sample(round)); + buf.endSession(); + } + expect(buf.pendingCount).toBe(2); + expect(warn).not.toHaveBeenCalled(); + + // Simulate a misuse where a retry prepends without first draining: + // queue would grow to 3, so the oldest-trailing entry must be evicted. + buf.prependBatch([sample(99)]); + expect(buf.pendingCount).toBe(2); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]?.[0]).toMatch(/prependBatch trimmed 1 trailing batch/); + + // Front is the prepended batch; the preserved trailing batch is round 1. + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([99]); + expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + expect(buf.pendingCount).toBe(0); + + warn.mockRestore(); + }); + it("reset() clears both active and pending state", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); buf.startSession(); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts index d812610..57db2ed 100644 --- a/src/lib/cursorTelemetryBuffer.ts +++ b/src/lib/cursorTelemetryBuffer.ts @@ -1,17 +1,89 @@ +/** + * A single cursor telemetry sample captured during a recording session. + * + * Coordinates (`cx`, `cy`) are device-pixel positions relative to the + * captured surface; `timeMs` is the offset from the recording's start. + */ export interface CursorTelemetryPoint { timeMs: number; cx: number; cy: number; } +/** + * Per-session cursor telemetry buffer with bounded memory. + * + * Flow: `startSession()` → `push(point)` N times → `endSession()` enqueues + * the collected samples as a completed batch. The main process later + * drains batches in FIFO order via `takeNextBatch()` to persist them to + * disk, and can `prependBatch()` on write failure to retry without losing + * order. + * + * Memory is bounded by `maxActiveSamples` (ring buffer on the in-progress + * batch) and `maxPendingBatches` (FIFO cap across completed batches). + */ export interface CursorTelemetryBuffer { + /** + * Begin a new recording session. Clears any in-progress active samples + * (without touching already-completed pending batches). Safe to call + * repeatedly — e.g. a rapid Stop → Record sequence. + */ startSession(): void; + + /** + * Append a telemetry sample to the current active session. When the + * active buffer exceeds `maxActiveSamples`, the oldest sample is + * dropped (ring behaviour). + */ push(point: CursorTelemetryPoint): void; - endSession(): void; + + /** + * Finalize the active session, moving its samples into the pending + * queue as a single batch. Empty sessions are dropped (no empty batch + * is enqueued). + * + * If the pending queue would exceed `maxPendingBatches`, the oldest + * batches are evicted to bound memory. A `console.warn` is emitted + * whenever at least one batch is dropped so that pathological rapid- + * restart scenarios are observable. + * + * @returns the number of pending batches dropped by this call (0 under + * normal operation). + */ + endSession(): number; + + /** + * Remove and return the oldest pending batch, or an empty array if + * the queue is empty. + */ takeNextBatch(): CursorTelemetryPoint[]; + + /** + * Re-insert a batch at the front of the queue, preserving FIFO order + * on retry paths (e.g. when persisting the batch failed and the + * caller wants the next `takeNextBatch()` to yield it again). + * + * Empty batches are ignored. The pending cap is enforced defensively + * — if prepending would push the queue past `maxPendingBatches`, the + * oldest entries are evicted and a `console.warn` is emitted. In + * normal retry usage this trim is a no-op because the caller has just + * removed the batch via `takeNextBatch()`. + */ prependBatch(batch: CursorTelemetryPoint[]): void; + + /** + * Drop the most recently enqueued pending batch. Used when a recording + * is discarded after `endSession()` but before it has been persisted. + * No-op on an empty queue. + */ discardLatestPending(): void; + + /** + * Clear both the active and pending state. Intended for tests and + * full teardown paths. + */ reset(): void; + readonly activeCount: number; readonly pendingCount: number; } @@ -23,6 +95,11 @@ export interface CursorTelemetryBufferOptions { const DEFAULT_MAX_PENDING_BATCHES = 8; +/** + * Create a cursor telemetry buffer. + * + * @see CursorTelemetryBuffer for the full lifecycle contract. + */ export function createCursorTelemetryBuffer( options: CursorTelemetryBufferOptions, ): CursorTelemetryBuffer { @@ -43,20 +120,37 @@ export function createCursorTelemetryBuffer( } }, endSession() { + let dropped = 0; if (active.length > 0) { pending.push(active); while (pending.length > maxPending) { pending.shift(); + dropped++; } } active = []; + if (dropped > 0) { + console.warn( + `[cursorTelemetryBuffer] dropped ${dropped} pending batch(es) to stay within maxPendingBatches=${maxPending}`, + ); + } + return dropped; }, takeNextBatch() { return pending.shift() ?? []; }, prependBatch(batch) { - if (batch.length > 0) { - pending.unshift(batch); + if (batch.length === 0) return; + pending.unshift(batch); + let dropped = 0; + while (pending.length > maxPending) { + pending.pop(); + dropped++; + } + if (dropped > 0) { + console.warn( + `[cursorTelemetryBuffer] prependBatch trimmed ${dropped} trailing batch(es) to stay within maxPendingBatches=${maxPending}`, + ); } }, discardLatestPending() { From 96765e483d6f39abd15c9cef89fd9c5a51795bef Mon Sep 17 00:00:00 2001 From: shaun0927 <70629228+shaun0927@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:12:28 +0900 Subject: [PATCH 113/152] docs: correct cx/cy units and sanitize buffer option limits Two follow-up fixes for CodeRabbit feedback on the docs commit: - CursorTelemetryPoint JSDoc previously described cx/cy as 'device-pixel positions'. The producer sampleCursorPoint() in electron/ipc/handlers.ts clamps them to the [0, 1] range after dividing by the source display's width/height, so they are normalised ratios, not pixel values. Correct the doc comment accordingly. - createCursorTelemetryBuffer now sanitizes maxActiveSamples and maxPendingBatches: non-finite, zero, or negative values fall back to safe positive-integer defaults. Without this, a caller passing Infinity or NaN would hang the trim loops. New test covers the sanitisation path for both options. --- src/lib/cursorTelemetryBuffer.test.ts | 23 +++++++++++++++++++++++ src/lib/cursorTelemetryBuffer.ts | 22 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts index 309df7e..567a1eb 100644 --- a/src/lib/cursorTelemetryBuffer.test.ts +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -190,6 +190,29 @@ describe("createCursorTelemetryBuffer", () => { warn.mockRestore(); }); + it("sanitizes non-finite or non-positive option values to safe defaults", () => { + // Infinity / NaN / negative would otherwise turn the trim loops + // into infinite loops. The buffer must fall back to defaults. + const buf = createCursorTelemetryBuffer({ + maxActiveSamples: Number.POSITIVE_INFINITY, + maxPendingBatches: Number.NaN, + }); + + buf.startSession(); + buf.push(sample(1)); + expect(() => buf.endSession()).not.toThrow(); + expect(buf.pendingCount).toBe(1); + + const buf2 = createCursorTelemetryBuffer({ + maxActiveSamples: -5, + maxPendingBatches: 0, + }); + buf2.startSession(); + buf2.push(sample(2)); + expect(() => buf2.endSession()).not.toThrow(); + expect(buf2.pendingCount).toBe(1); + }); + it("reset() clears both active and pending state", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); buf.startSession(); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts index 57db2ed..e97bab8 100644 --- a/src/lib/cursorTelemetryBuffer.ts +++ b/src/lib/cursorTelemetryBuffer.ts @@ -1,8 +1,10 @@ /** * A single cursor telemetry sample captured during a recording session. * - * Coordinates (`cx`, `cy`) are device-pixel positions relative to the - * captured surface; `timeMs` is the offset from the recording's start. + * Coordinates (`cx`, `cy`) are clamped ratios in the `[0, 1]` range, + * normalised against the captured surface's width and height by the + * main-process `sampleCursorPoint()` before being pushed. `timeMs` is the + * offset (in milliseconds) from the recording's start. */ export interface CursorTelemetryPoint { timeMs: number; @@ -94,17 +96,29 @@ export interface CursorTelemetryBufferOptions { } const DEFAULT_MAX_PENDING_BATCHES = 8; +const DEFAULT_MAX_ACTIVE_SAMPLES = 10_000; + +/** Coerce a numeric option into a safe, finite, positive integer. */ +function sanitizeLimit(value: number | undefined, fallback: number): number { + if (typeof value !== "number" || !Number.isFinite(value)) return fallback; + const floored = Math.floor(value); + return floored >= 1 ? floored : fallback; +} /** * Create a cursor telemetry buffer. * + * Numeric options are sanitized: non-finite, negative, or zero values fall + * back to safe defaults so a bad caller cannot disable the memory bounds + * (which would turn the trim loops into infinite loops). + * * @see CursorTelemetryBuffer for the full lifecycle contract. */ export function createCursorTelemetryBuffer( options: CursorTelemetryBufferOptions, ): CursorTelemetryBuffer { - const maxActive = options.maxActiveSamples; - const maxPending = options.maxPendingBatches ?? DEFAULT_MAX_PENDING_BATCHES; + const maxActive = sanitizeLimit(options.maxActiveSamples, DEFAULT_MAX_ACTIVE_SAMPLES); + const maxPending = sanitizeLimit(options.maxPendingBatches, DEFAULT_MAX_PENDING_BATCHES); let active: CursorTelemetryPoint[] = []; let pending: CursorTelemetryPoint[][] = []; From 9e345660e694bc22bebaf0b70f121b20d94d506c Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 12:27:13 +0200 Subject: [PATCH 114/152] chore: update dependencies to latest versions --- package-lock.json | 4490 ++++++++++++++++++--------------------------- package.json | 73 +- 2 files changed, 1789 insertions(+), 2774 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba40beb..4e1b349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,69 +16,68 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@types/gif.js": "^0.2.5", - "@uiw/color-convert": "^2.9.2", - "@uiw/react-color-block": "^2.9.2", + "@uiw/color-convert": "^2.10.1", + "@uiw/react-color-block": "^2.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dnd-timeline": "^2.2.0", - "emoji-picker-react": "^4.16.1", + "dnd-timeline": "^2.4.0", + "emoji-picker-react": "^4.18.0", "fix-webm-duration": "^1.0.6", "gif.js": "^0.2.0", - "gsap": "^3.13.0", + "gsap": "^3.15.0", "lucide-react": "^0.545.0", - "mediabunny": "^1.25.1", - "motion": "^12.23.24", - "mp4box": "^2.2.0", + "mediabunny": "^1.40.1", + "motion": "^12.38.0", + "mp4box": "^2.3.0", "pixi-filters": "^6.1.5", - "pixi.js": "^8.14.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^5.5.0", + "pixi.js": "^8.18.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-icons": "^5.6.0", "react-resizable-panels": "^3.0.6", - "react-rnd": "^10.5.2", + "react-rnd": "^10.5.3", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", + "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", "uuid": "^13.0.0", "web-demuxer": "^4.0.0" }, "devDependencies": { - "@biomejs/biome": "^2.3.13", + "@biomejs/biome": "^2.4.12", "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "^18.2.64", - "@types/react-dom": "^18.2.21", - "@types/uuid": "^10.0.0", - "@vitejs/plugin-react": "^4.2.1", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "autoprefixer": "^10.4.21", - "electron": "^39.2.7", - "electron-builder": "^26.7.0", + "@types/node": "^25.6.0", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", + "@vitest/browser": "^4.1.4", + "@vitest/browser-playwright": "^4.1.4", + "autoprefixer": "^10.5.0", + "electron": "^41.2.1", + "electron-builder": "^26.8.1", "electron-icon-builder": "^2.0.1", "electron-rebuild": "^3.2.9", - "fast-check": "^4.5.2", + "fast-check": "^4.7.0", "husky": "^9.1.7", - "jsdom": "^29.0.1", - "lint-staged": "^16.3.2", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.18", - "terser": "^5.44.1", - "typescript": "^5.2.2", - "vite": "^5.1.6", - "vite-plugin-electron": "^0.28.6", - "vite-plugin-electron-renderer": "^0.14.5", - "vitest": "^4.0.16" + "jsdom": "^29.0.2", + "lint-staged": "^16.4.0", + "postcss": "^8.5.10", + "tailwindcss": "^3.4.19", + "terser": "^5.46.1", + "typescript": "^5.9.3", + "vite": "^5.4.21", + "vite-plugin-electron": "^0.28.8", + "vite-plugin-electron-renderer": "^0.14.6", + "vitest": "^4.1.4" }, "engines": { "node": "22.22.1", @@ -105,57 +104,47 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", - "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.1.1", - "@csstools/css-color-parser": "^4.0.2", + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.6" + "@csstools/css-tokenizer": "^4.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", - "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, "license": "MIT", "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7" + "is-potential-custom-element-name": "^1.0.1" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -166,13 +155,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -181,9 +170,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -191,21 +180,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -232,14 +221,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -249,13 +238,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -265,6 +254,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -275,6 +274,13 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -286,29 +292,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -318,9 +324,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -338,9 +344,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -358,27 +364,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -429,33 +435,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -463,23 +469,23 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@biomejs/biome": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.13.tgz", - "integrity": "sha512-Fw7UsV0UAtWIBIm0M7g5CRerpu1eKyKAXIazzxhbXYUyMkwNrkX/KLkGI7b+uVDQ5cLUMfOC9vR60q9IDYDstA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.12.tgz", + "integrity": "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -493,20 +499,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.13", - "@biomejs/cli-darwin-x64": "2.3.13", - "@biomejs/cli-linux-arm64": "2.3.13", - "@biomejs/cli-linux-arm64-musl": "2.3.13", - "@biomejs/cli-linux-x64": "2.3.13", - "@biomejs/cli-linux-x64-musl": "2.3.13", - "@biomejs/cli-win32-arm64": "2.3.13", - "@biomejs/cli-win32-x64": "2.3.13" + "@biomejs/cli-darwin-arm64": "2.4.12", + "@biomejs/cli-darwin-x64": "2.4.12", + "@biomejs/cli-linux-arm64": "2.4.12", + "@biomejs/cli-linux-arm64-musl": "2.4.12", + "@biomejs/cli-linux-x64": "2.4.12", + "@biomejs/cli-linux-x64-musl": "2.4.12", + "@biomejs/cli-win32-arm64": "2.4.12", + "@biomejs/cli-win32-x64": "2.4.12" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.13.tgz", - "integrity": "sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", + "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", "cpu": [ "arm64" ], @@ -521,9 +527,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.13.tgz", - "integrity": "sha512-AGr8OoemT/ejynbIu56qeil2+F2WLkIjn2d8jGK1JkchxnMUhYOfnqc9sVzcRxpG9Ycvw4weQ5sprRvtb7Yhcw==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", + "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", "cpu": [ "x64" ], @@ -538,9 +544,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.13.tgz", - "integrity": "sha512-xvOiFkrDNu607MPMBUQ6huHmBG1PZLOrqhtK6pXJW3GjfVqJg0Z/qpTdhXfcqWdSZHcT+Nct2fOgewZvytESkw==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", + "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", "cpu": [ "arm64" ], @@ -555,9 +561,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.13.tgz", - "integrity": "sha512-TUdDCSY+Eo/EHjhJz7P2GnWwfqet+lFxBZzGHldrvULr59AgahamLs/N85SC4+bdF86EhqDuuw9rYLvLFWWlXA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", + "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", "cpu": [ "arm64" ], @@ -572,9 +578,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.13.tgz", - "integrity": "sha512-s+YsZlgiXNq8XkgHs6xdvKDFOj/bwTEevqEY6rC2I3cBHbxXYU1LOZstH3Ffw9hE5tE1sqT7U23C00MzkXztMw==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", + "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", "cpu": [ "x64" ], @@ -589,9 +595,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.13.tgz", - "integrity": "sha512-0bdwFVSbbM//Sds6OjtnmQGp4eUjOTt6kHvR/1P0ieR9GcTUAlPNvPC3DiavTqq302W34Ae2T6u5VVNGuQtGlQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", + "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", "cpu": [ "x64" ], @@ -606,9 +612,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.13.tgz", - "integrity": "sha512-QweDxY89fq0VvrxME+wS/BXKmqMrOTZlN9SqQ79kQSIc3FrEwvW/PvUegQF6XIVaekncDykB5dzPqjbwSKs9DA==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", + "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", "cpu": [ "arm64" ], @@ -623,9 +629,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.13.tgz", - "integrity": "sha512-trDw2ogdM2lyav9WFQsdsfdVy1dvZALymRpgmWsvSez0BJzBjulhOT/t+wyKeh3pZWvwP3VMs1SoOKwO3wecMQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", + "integrity": "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==", "cpu": [ "x64" ], @@ -639,6 +645,13 @@ "node": ">=14.21.3" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -673,9 +686,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", - "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", "dev": true, "funding": [ { @@ -697,9 +710,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", - "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, "funding": [ { @@ -714,7 +727,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.1.1" + "@csstools/css-calc": "^3.2.0" }, "engines": { "node": ">=20.19.0" @@ -748,9 +761,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", - "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", "dev": true, "funding": [ { @@ -868,9 +881,9 @@ } }, "node_modules/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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": { @@ -879,9 +892,9 @@ } }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -923,9 +936,9 @@ } }, "node_modules/@electron/fuses/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1009,9 +1022,9 @@ } }, "node_modules/@electron/notarize/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1082,9 +1095,9 @@ } }, "node_modules/@electron/osx-sign/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1264,12 +1277,28 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@electron/rebuild/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/@electron/rebuild/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/rebuild/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -1329,9 +1358,9 @@ } }, "node_modules/@electron/rebuild/node_modules/node-abi": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.26.0.tgz", - "integrity": "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", + "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", "dev": true, "license": "MIT", "dependencies": { @@ -1419,9 +1448,9 @@ } }, "node_modules/@electron/rebuild/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -1507,9 +1536,9 @@ } }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -1522,9 +1551,9 @@ } }, "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1534,6 +1563,22 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@electron/universal/node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -1567,9 +1612,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "optional": true, @@ -1584,9 +1629,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "optional": true, @@ -1610,6 +1655,40 @@ "node": ">= 10.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1899,22 +1978,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -1932,22 +1995,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -1965,22 +2012,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -2087,31 +2118,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -2119,9 +2150,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@gar/promisify": { @@ -2135,6 +2166,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -2152,6 +2184,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2164,6 +2197,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2176,12 +2210,14 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -2196,12 +2232,13 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -2214,6 +2251,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -2241,11 +2279,11 @@ } }, "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -2869,9 +2907,9 @@ } }, "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2891,6 +2929,25 @@ "node": ">= 10.0.0" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3032,6 +3089,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@oxc-project/types": { + "version": "0.126.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", + "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@pixi/color": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", @@ -3183,6 +3250,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -3332,6 +3400,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -3398,6 +3484,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -3567,6 +3671,24 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", @@ -3604,6 +3726,24 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -3707,6 +3847,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -3781,6 +3939,24 @@ } } }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", @@ -3815,9 +3991,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -3979,6 +4155,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -4144,6 +4338,263 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz", + "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz", + "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -4152,9 +4603,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], @@ -4166,9 +4617,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], @@ -4180,9 +4631,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], @@ -4194,9 +4645,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], @@ -4208,9 +4659,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], @@ -4222,9 +4673,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], @@ -4236,9 +4687,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], @@ -4250,9 +4701,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], @@ -4264,9 +4715,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], @@ -4278,9 +4729,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", "cpu": [ "arm64" ], @@ -4292,9 +4743,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", "cpu": [ "loong64" ], @@ -4306,9 +4771,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", "cpu": [ "ppc64" ], @@ -4320,9 +4799,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], @@ -4334,9 +4813,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], @@ -4348,9 +4827,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], @@ -4362,9 +4841,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], @@ -4376,9 +4855,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", "cpu": [ "x64" ], @@ -4389,10 +4868,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", "cpu": [ "arm64" ], @@ -4404,9 +4897,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], @@ -4418,9 +4911,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], @@ -4432,9 +4925,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", "cpu": [ "x64" ], @@ -4446,9 +4939,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], @@ -4476,7 +4969,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", @@ -4598,6 +5092,17 @@ "node": ">= 10" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -4669,6 +5174,7 @@ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, + "license": "MIT", "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" @@ -4678,12 +5184,13 @@ "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, "license": "MIT", "dependencies": { @@ -4694,7 +5201,8 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/dom-mediacapture-transform": { "version": "0.1.11", @@ -4780,13 +5288,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/plist": { @@ -4809,14 +5317,14 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.26", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", - "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { @@ -4839,13 +5347,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/verror": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", @@ -4866,9 +5367,9 @@ } }, "node_modules/@uiw/color-convert": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.2.tgz", - "integrity": "sha512-ibw9OS29S7GlL+vDwU3p5XG3vhR7XdzUecydpZbakUeg2Td6nfsnrCAX9sbLwQ73p0abO42v+V4qRaWq+7/BjQ==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.10.1.tgz", + "integrity": "sha512-/Z3YfBiX+SErRM59yQH88Id+Xy/k10nnkfTuqhX6RB2yYUcG57DoFqb6FudhiQ5fwzKvKf1k4xq9lfT1UTFUKQ==", "license": "MIT", "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" @@ -4878,14 +5379,14 @@ } }, "node_modules/@uiw/react-color-block": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.9.2.tgz", - "integrity": "sha512-0EIZTELA5pnxyMlBOFo3hrpy73db+Qeq6E+QptNfD/8izor8OvY1Uquj2VqD6gDz+iVHMELIoKxpaQ8sZR7NOg==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.10.1.tgz", + "integrity": "sha512-nGfhUGZhCbYH/gVvD12H9wZ/NBiToiSyrSdbracyIbA01tZInmkRNmfzTCkPq7yoXMa09I9G8pPISoyHre4TQg==", "license": "MIT", "dependencies": { - "@uiw/color-convert": "2.9.2", - "@uiw/react-color-editable-input": "2.9.2", - "@uiw/react-color-swatch": "2.9.2" + "@uiw/color-convert": "2.10.1", + "@uiw/react-color-editable-input": "2.10.1", + "@uiw/react-color-swatch": "2.10.1" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" @@ -4897,9 +5398,9 @@ } }, "node_modules/@uiw/react-color-editable-input": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.9.2.tgz", - "integrity": "sha512-DY7pu12+LDRn6cxDMvsy1/quaPTxicAPz/kfODV7KBf8+Hq4rFWeJ4KS6m22IKIbQxrBQgmQG0WFJLaPeY7cPw==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.10.1.tgz", + "integrity": "sha512-jMim8eAw/5hz7gaZwBy3vM5wMxPMocOG+u1+wcKbqvavHaeg/wHq7Y29uRyFKj80s4FXvUKehXRQl0F68mA7jQ==", "license": "MIT", "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" @@ -4911,12 +5412,12 @@ } }, "node_modules/@uiw/react-color-swatch": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.9.2.tgz", - "integrity": "sha512-6zBy+E9NzZR672M2wPsbbNRqKy9Wi9jOuuxxyzov1CEZp+pPX7UwMlCX6RUhKdO0PzTSPCVQmbz5bplu5vsW0w==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.10.1.tgz", + "integrity": "sha512-DuGlaIszNcvtsY8BfW+RiUkEK1yVmnAamkzc/S5cQZwaAA5bCKhwwyaKPqh1/XWs7pR4pysjSNlMaeqaSOO34A==", "license": "MIT", "dependencies": { - "@uiw/color-convert": "2.9.2" + "@uiw/color-convert": "2.10.1" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" @@ -4949,43 +5450,45 @@ } }, "node_modules/@vitest/browser": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", - "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.4.tgz", + "integrity": "sha512-TrNaY/yVOwxtrxNsDUC/wQ56xSwplpytTeRAqF/197xV/ZddxxulBsxR6TrhVMyniJmp9in8d5u0AcDaNRY30w==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/mocker": "4.0.16", - "@vitest/utils": "4.0.16", + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.4", + "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", - "pixelmatch": "7.1.0", "pngjs": "^7.0.0", "sirv": "^3.0.2", - "tinyrainbow": "^3.0.3", - "ws": "^8.18.3" + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.0.16" + "vitest": "4.1.4" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", - "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.4.tgz", + "integrity": "sha512-q3PchVhZINX23Pv+RERgAtDlp6wzVkID/smOPnZ5YGWpeWUe3jMNYppeVh15j4il3G7JIJty1d1Kicpm0HSMig==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/browser": "4.0.16", - "@vitest/mocker": "4.0.16", - "tinyrainbow": "^3.0.3" + "@vitest/browser": "4.1.4", + "@vitest/mocker": "4.1.4", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "playwright": "*", - "vitest": "4.0.16" + "vitest": "4.1.4" }, "peerDependenciesMeta": { "playwright": { @@ -4993,481 +5496,14 @@ } } }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -5476,7 +5512,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -5487,637 +5523,14 @@ } } }, - "node_modules/@vitest/browser-playwright/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@vitest/browser-playwright/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@vitest/browser-playwright/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@vitest/browser/node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -6126,7 +5539,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -6137,219 +5550,55 @@ } } }, - "node_modules/@vitest/browser/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/@vitest/browser/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@vitest/browser/node_modules/pixelmatch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", - "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", - "dev": true, - "dependencies": { - "pngjs": "^7.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, "node_modules/@vitest/browser/node_modules/pngjs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.19.0" } }, - "node_modules/@vitest/browser/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", - "dev": true, - "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", "dev": true, + "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.1.4", "pathe": "^2.0.3" }, "funding": { @@ -6357,12 +5606,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -6371,37 +5622,40 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", "dev": true, + "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", - "tinyrainbow": "^3.0.3" + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@webgpu/types": { - "version": "0.1.66", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", - "integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==", + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", "license": "BSD-3-Clause" }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6488,9 +5742,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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": { @@ -6534,6 +5788,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6543,6 +5798,7 @@ "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" @@ -6588,9 +5844,9 @@ "license": "MIT" }, "node_modules/app-builder-lib": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.7.0.tgz", - "integrity": "sha512-/UgCD8VrO79Wv8aBNpjMfsS1pIUfIPURoRn0Ik6tMe5avdZF+vQgl/juJgipcMmH3YS0BD573lCdCHyoi84USg==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", + "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", "dev": true, "license": "MIT", "dependencies": { @@ -6605,7 +5861,7 @@ "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", - "builder-util": "26.4.1", + "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", @@ -6613,7 +5869,7 @@ "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", - "electron-publish": "26.6.0", + "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", @@ -6635,8 +5891,8 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "26.7.0", - "electron-builder-squirrel-windows": "26.7.0" + "dmg-builder": "26.8.1", + "electron-builder-squirrel-windows": "26.8.1" } }, "node_modules/app-builder-lib/node_modules/@electron/get": { @@ -6686,42 +5942,6 @@ "semver": "bin/semver.js" } }, - "node_modules/app-builder-lib/node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/app-builder-lib/node_modules/balanced-match": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.2.tgz", - "integrity": "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==", - "dev": true, - "license": "MIT", - "dependencies": { - "jackspeak": "^4.2.3" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/app-builder-lib/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/app-builder-lib/node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -6764,9 +5984,9 @@ } }, "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6796,54 +6016,12 @@ "node": ">=18" } }, - "node_modules/app-builder-lib/node_modules/jackspeak": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", - "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^9.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/app-builder-lib/node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/app-builder-lib/node_modules/minimatch": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.0.tgz", - "integrity": "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/app-builder-lib/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6862,9 +6040,9 @@ } }, "node_modules/app-builder-lib/node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -7090,6 +6268,7 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -7140,9 +6319,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", "dev": true, "funding": [ { @@ -7160,10 +6339,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -7198,6 +6376,7 @@ "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/base64-js": { @@ -7222,13 +6401,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.15", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.15.tgz", - "integrity": "sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==", + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bcrypt-pbkdf": { @@ -7292,9 +6474,10 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7313,9 +6496,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -7333,11 +6516,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -7389,9 +6572,9 @@ "license": "MIT" }, "node_modules/builder-util": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.4.1.tgz", - "integrity": "sha512-FlgH43XZ50w3UtS1RVGDWOz8v9qMXPC7upMtKMtBEnYdt1OVoS61NYhKm/4x+cIaWqJTXua0+VVPI+fSPGXNIw==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", + "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", "dev": true, "license": "MIT", "dependencies": { @@ -7481,9 +6664,9 @@ } }, "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7565,9 +6748,9 @@ } }, "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -7590,13 +6773,6 @@ "node": ">=8" } }, - "node_modules/cacache/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -7676,9 +6852,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001749", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz", - "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==", + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", "dev": true, "funding": [ { @@ -7718,6 +6894,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -7935,6 +7112,7 @@ "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" @@ -7947,6 +7125,7 @@ "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/color-support": { @@ -8100,6 +7279,7 @@ "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", @@ -8144,9 +7324,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, "license": "MIT" }, @@ -8394,9 +7574,9 @@ } }, "node_modules/dir-compare/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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": { @@ -8405,9 +7585,9 @@ } }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -8437,14 +7617,14 @@ "license": "MIT" }, "node_modules/dmg-builder": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.7.0.tgz", - "integrity": "sha512-uOOBA3f+kW3o4KpSoMQ6SNpdXU7WtxlJRb9vCZgOvqhTz4b3GjcoWKstdisizNZLsylhTMv8TLHFPFW0Uxsj/g==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", + "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" @@ -8469,9 +7649,9 @@ } }, "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8519,9 +7699,9 @@ } }, "node_modules/dnd-timeline": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dnd-timeline/-/dnd-timeline-2.2.0.tgz", - "integrity": "sha512-bQ/2bm70eA7YeztgdxoSpdpTwPzj8VT2/wTlYrnFpqJ71et7EVJZR35XPZIVzBqSyKK9T/QyUzE26gYck9ldxg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dnd-timeline/-/dnd-timeline-2.4.0.tgz", + "integrity": "sha512-f71y55DjT2VDongtnqVd7S6XGmpUT1PkYlooFQZxLHfPVfKsyTtMHsfbXN98k2GkA5Z4h3ibqFjwBIfckE0aEg==", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.1.0", @@ -8596,6 +7776,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/ecc-jsbn": { @@ -8626,15 +7807,15 @@ } }, "node_modules/electron": { - "version": "39.2.7", - "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.7.tgz", - "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==", + "version": "41.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-41.2.1.tgz", + "integrity": "sha512-teeRThiYGTPKf/2yOW7zZA1bhb91KEQ4yLBPOg7GxpmnkLFLugKgQaAKOrCgdzwsXh/5mFIfmkm+4+wACJKwaA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", + "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { @@ -8645,18 +7826,18 @@ } }, "node_modules/electron-builder": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.7.0.tgz", - "integrity": "sha512-LoXbCvSFxLesPneQ/fM7FB4OheIDA2tjqCdUkKlObV5ZKGhYgi5VHPHO/6UUOUodAlg7SrkPx7BZJPby+Vrtbg==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", + "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", - "dmg-builder": "26.7.0", + "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", @@ -8671,15 +7852,15 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "26.7.0", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.7.0.tgz", - "integrity": "sha512-3EqkQK+q0kGshdPSKEPb2p5F75TENMKu6Fe5aTdeaPfdzFK4Yjp5L0d6S7K8iyvqIsGQ/ei4bnpyX9wt+kVCKQ==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", + "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "26.7.0", - "builder-util": "26.4.1", + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, @@ -8740,14 +7921,14 @@ } }, "node_modules/electron-publish": { - "version": "26.6.0", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.6.0.tgz", - "integrity": "sha512-LsyHMMqbvJ2vsOvuWJ19OezgF2ANdCiHpIucDHNiLhuI+/F3eW98ouzWSRmXXi82ZOPZXC07jnIravY4YYwCLQ==", + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", + "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", "dev": true, "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "26.4.1", + "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", @@ -8772,9 +7953,9 @@ } }, "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8863,9 +8044,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.234", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz", - "integrity": "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg==", + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", "dev": true, "license": "ISC" }, @@ -8908,26 +8089,26 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/electron/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, "node_modules/emoji-picker-react": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.16.1.tgz", - "integrity": "sha512-MrPX0tOCfRL3uYI4of/2GRZ7S6qS7YlacKiF78uFH84/C62vcuHE2DZyv5b4ZJMk0e06es1jjB4e31Bb+YSM8w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.18.0.tgz", + "integrity": "sha512-vLTrLfApXAIciguGE57pXPWs9lPLBspbEpPMiUq03TIli2dHZBiB+aZ0R9/Wat0xmTfcd4AuEzQgSYxEZ8C88Q==", "license": "MIT", "dependencies": { "flairup": "1.0.0" @@ -8943,6 +8124,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encoding": { @@ -8967,13 +8149,13 @@ } }, "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -9038,10 +8220,11 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -9154,6 +8337,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -9247,9 +8431,9 @@ "optional": true }, "node_modules/fast-check": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.5.2.tgz", - "integrity": "sha512-tOzL01LMrDIWPLfvMiGUMH0AjqnOelHQPmgvYkW/aRO4Yaw+pBQqWmyebNzAEbKOigoCN8HkRWUZXFkjmiaXMQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", + "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", "dev": true, "funding": [ { @@ -9263,7 +8447,7 @@ ], "license": "MIT", "dependencies": { - "pure-rand": "^7.0.0" + "pure-rand": "^8.0.0" }, "engines": { "node": ">=12.17.0" @@ -9359,9 +8543,9 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -9369,9 +8553,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -9406,9 +8590,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -9430,6 +8614,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -9470,27 +8655,27 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, "node_modules/framer-motion": { - "version": "12.23.24", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", - "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", "license": "MIT", "dependencies": { - "motion-dom": "^12.23.23", - "motion-utils": "^12.23.6", + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -9551,13 +8736,6 @@ "node": ">=8" } }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9782,9 +8960,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "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": { @@ -9793,9 +8971,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": { @@ -9920,9 +9098,9 @@ "license": "ISC" }, "node_modules/gsap": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz", - "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", + "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, "node_modules/har-schema": { @@ -10048,26 +9226,6 @@ "node": ">=10" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -10406,6 +9564,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10544,6 +9703,7 @@ "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/ismobilejs": { @@ -10563,6 +9723,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -10607,12 +9768,13 @@ } }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/jpeg-js": { @@ -10655,14 +9817,14 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", @@ -10696,9 +9858,9 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -10880,6 +10042,267 @@ "node": ">=0.10.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -10899,17 +10322,17 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.3.2.tgz", - "integrity": "sha512-xKqhC2AeXLwiAHXguxBjuChoTTWFC6Pees0SHPwOpwlvI3BH7ZADFPddAdN3pgo3aiKgPUx/bxE78JfUnxQnlg==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", "dev": true, "license": "MIT", "dependencies": { "commander": "^14.0.3", "listr2": "^9.0.5", - "micromatch": "^4.0.8", + "picomatch": "^4.0.3", "string-argv": "^0.3.2", - "tinyexec": "^1.0.2", + "tinyexec": "^1.0.4", "yaml": "^2.8.2" }, "bin": { @@ -10932,6 +10355,19 @@ "node": ">=20" } }, + "node_modules/lint-staged/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", @@ -11174,9 +10610,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -11407,13 +10843,16 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/lucide-react": { @@ -11523,13 +10962,6 @@ "node": ">=8" } }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -11561,9 +10993,9 @@ "license": "CC0-1.0" }, "node_modules/mediabunny": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.1.tgz", - "integrity": "sha512-0Rrd47PMCVJbTPA7IJaXPCupV5/RZ/icgr+a0qExRJAr0n5vB4fsGSo+fdHIehG0CrddXtVRvNZwFtJz709yfA==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.40.1.tgz", + "integrity": "sha512-HU/stGzAkdWaJIly6ypbUVgAUvT9kt39DIg0IaErR7/1fwtTmgUYs4i8uEPYcgcjPjbB9gtBmUXOLnXi6J2LDw==", "license": "MPL-2.0", "workspaces": [ "packages/*" @@ -11695,20 +11127,44 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "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": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/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/minimatch/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/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -11723,6 +11179,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -11754,13 +11211,6 @@ "node": ">=8" } }, - "node_modules/minipass-collect/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", @@ -11792,13 +11242,6 @@ "node": ">=8" } }, - "node_modules/minipass-fetch/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -11825,13 +11268,6 @@ "node": ">=8" } }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -11858,13 +11294,6 @@ "node": ">=8" } }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", @@ -11891,13 +11320,6 @@ "node": ">=8" } }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -11925,13 +11347,6 @@ "node": ">=8" } }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -11946,12 +11361,12 @@ } }, "node_modules/motion": { - "version": "12.23.24", - "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz", - "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==", + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", "license": "MIT", "dependencies": { - "framer-motion": "^12.23.24", + "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -11972,24 +11387,24 @@ } }, "node_modules/motion-dom": { - "version": "12.23.23", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", - "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", "license": "MIT", "dependencies": { - "motion-utils": "^12.23.6" + "motion-utils": "^12.36.0" } }, "node_modules/motion-utils": { - "version": "12.23.6", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", - "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", "license": "MIT" }, "node_modules/mp4box": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mp4box/-/mp4box-2.2.0.tgz", - "integrity": "sha512-tE+L7wdhSuwBKZGjUzj03Qzj4lWyOw8pHSPyLnvHTKx92NJGkJls0pcEusUHWEh5gWVBlhdu79STJh4Bubz9mQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/mp4box/-/mp4box-2.3.0.tgz", + "integrity": "sha512-nnABYbdh4UguEYyV+uRwQBi1tbb8kXka2Fx9yKzmDKAeh8gkvRKYxoK1XDd8GQIjSfN4rvsXrW1CBo4yRQJZDA==", "license": "BSD-3-Clause", "engines": { "node": ">=20.8.1" @@ -12130,9 +11545,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -12191,16 +11606,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -12420,6 +11825,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { @@ -12481,13 +11887,13 @@ "license": "MIT" }, "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^6.0.0" + "entities": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -12507,6 +11913,7 @@ "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" @@ -12522,6 +11929,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -12538,6 +11946,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/path-type": { @@ -12554,7 +11963,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pe-library": { "version": "0.4.1", @@ -12727,9 +12137,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -12815,22 +12225,25 @@ } }, "node_modules/pixi.js": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.14.0.tgz", - "integrity": "sha512-ituDiEBb1Oqx56RYwTtC6MjPUhPfF/i15fpUv5oEqmzC/ce3SaSumulJcOjKG7+y0J0Ekl9Rl4XTxaUw+MVFZw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.18.1.tgz", + "integrity": "sha512-6LUPWYgulZhp/w4kam2XHXB0QedISZIqrJbRdHLLQ3csn5a38uzKxAp6B5j6s89QFYaIJbg95kvgTRcbgpO1ow==", "license": "MIT", + "workspaces": [ + "examples", + "playground" + ], "dependencies": { "@pixi/colord": "^2.9.6", - "@types/css-font-loading-module": "^0.0.12", "@types/earcut": "^3.0.0", - "@webgpu/types": "^0.1.40", - "@xmldom/xmldom": "^0.8.10", + "@webgpu/types": "^0.1.69", + "@xmldom/xmldom": "^0.8.12", "earcut": "^3.0.2", "eventemitter3": "^5.0.1", "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2", - "tiny-lru": "^11.4.5" + "tiny-lru": "^11.4.7" }, "funding": { "type": "opencollective", @@ -12914,9 +12327,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "funding": [ { "type": "opencollective", @@ -12931,6 +12344,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13259,9 +12673,9 @@ } }, "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", "dev": true, "funding": [ { @@ -13276,9 +12690,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "peer": true, "dependencies": { @@ -13360,12 +12774,12 @@ } }, "node_modules/react-draggable": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", - "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", + "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", "license": "MIT", "dependencies": { - "clsx": "^1.1.1", + "clsx": "^2.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { @@ -13373,19 +12787,10 @@ "react-dom": ">= 16.3.0" } }, - "node_modules/react-draggable/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/react-icons": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", "license": "MIT", "peerDependencies": { "react": "*" @@ -13465,13 +12870,13 @@ } }, "node_modules/react-rnd": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", - "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "version": "10.5.3", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.3.tgz", + "integrity": "sha512-s/sIT3pGZnQ+57egijkTp9mizjIWrJz68Pq6yd+F/wniFY3IriML18dUXnQe/HP9uMiJ+9MAp44hljG99fZu6Q==", "license": "MIT", "dependencies": { - "re-resizable": "6.11.2", - "react-draggable": "4.4.6", + "re-resizable": "^6.11.2", + "react-draggable": "^4.5.0", "tslib": "2.6.2" }, "peerDependencies": { @@ -13766,9 +13171,9 @@ } }, "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -13961,10 +13366,51 @@ "node": ">=8.0" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz", + "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.126.0", + "@rolldown/pluginutils": "1.0.0-rc.16" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-x64": "1.0.0-rc.16", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz", + "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13978,28 +13424,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, @@ -14055,9 +13504,9 @@ "license": "MIT" }, "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", "dev": true, "license": "WTFPL OR ISC", "dependencies": { @@ -14156,6 +13605,7 @@ "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" @@ -14168,6 +13618,7 @@ "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" @@ -14260,6 +13711,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -14498,13 +13950,6 @@ "node": ">=8" } }, - "node_modules/ssri/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -14523,10 +13968,11 @@ } }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -14552,6 +13998,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14567,6 +14014,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14581,6 +14029,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14594,6 +14043,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14647,17 +14097,17 @@ } }, "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -14677,35 +14127,6 @@ "node": ">= 6" } }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -14901,9 +14322,9 @@ "license": "MIT" }, "node_modules/tailwind-merge": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", - "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", "license": "MIT", "funding": { "type": "github", @@ -14911,9 +14332,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", - "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -14956,6 +14377,15 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -14974,13 +14404,6 @@ "node": ">=10" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -15023,9 +14446,9 @@ } }, "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -15075,9 +14498,9 @@ } }, "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -15159,9 +14582,9 @@ } }, "node_modules/tiny-lru": { - "version": "11.4.5", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.5.tgz", - "integrity": "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw==", + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz", + "integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==", "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -15182,9 +14605,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", "engines": { @@ -15192,14 +14615,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -15212,7 +14634,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -15227,10 +14648,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -15244,27 +14664,28 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tldts": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", - "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.27" + "tldts-core": "^7.0.28" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", - "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", "dev": true, "license": "MIT" }, @@ -15418,9 +14839,9 @@ } }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -15428,9 +14849,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, @@ -15471,9 +14892,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -15721,30 +15142,31 @@ "license": "MIT" }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -15760,12 +15182,15 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -15786,6 +15211,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -15794,384 +15225,20 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, - "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -16180,7 +15247,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -16191,69 +15258,12 @@ } } }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/vitest/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -16262,23 +15272,23 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz", + "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.16", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -16287,14 +15297,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -16303,15 +15314,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -16406,6 +15420,7 @@ "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" @@ -16474,6 +15489,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -16607,16 +15623,16 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "devOptional": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index d41fd40..2d77400 100644 --- a/package.json +++ b/package.json @@ -40,69 +40,68 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "@types/gif.js": "^0.2.5", - "@uiw/color-convert": "^2.9.2", - "@uiw/react-color-block": "^2.9.2", + "@uiw/color-convert": "^2.10.1", + "@uiw/react-color-block": "^2.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dnd-timeline": "^2.2.0", - "emoji-picker-react": "^4.16.1", + "dnd-timeline": "^2.4.0", + "emoji-picker-react": "^4.18.0", "fix-webm-duration": "^1.0.6", "gif.js": "^0.2.0", - "gsap": "^3.13.0", + "gsap": "^3.15.0", "lucide-react": "^0.545.0", - "mediabunny": "^1.25.1", - "motion": "^12.23.24", - "mp4box": "^2.2.0", + "mediabunny": "^1.40.1", + "motion": "^12.38.0", + "mp4box": "^2.3.0", "pixi-filters": "^6.1.5", - "pixi.js": "^8.14.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^5.5.0", + "pixi.js": "^8.18.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-icons": "^5.6.0", "react-resizable-panels": "^3.0.6", - "react-rnd": "^10.5.2", + "react-rnd": "^10.5.3", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", + "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", "uuid": "^13.0.0", "web-demuxer": "^4.0.0" }, "devDependencies": { - "@biomejs/biome": "^2.3.13", + "@biomejs/biome": "^2.4.12", "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.0.3", - "@types/react": "^18.2.64", - "@types/react-dom": "^18.2.21", - "@types/uuid": "^10.0.0", - "@vitejs/plugin-react": "^4.2.1", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "autoprefixer": "^10.4.21", - "electron": "^39.2.7", - "electron-builder": "^26.7.0", + "@types/node": "^25.6.0", + "@types/react": "^18.3.28", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", + "@vitest/browser": "^4.1.4", + "@vitest/browser-playwright": "^4.1.4", + "autoprefixer": "^10.5.0", + "electron": "^41.2.1", + "electron-builder": "^26.8.1", "electron-icon-builder": "^2.0.1", "electron-rebuild": "^3.2.9", - "fast-check": "^4.5.2", + "fast-check": "^4.7.0", "husky": "^9.1.7", - "jsdom": "^29.0.1", - "lint-staged": "^16.3.2", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.18", - "terser": "^5.44.1", - "typescript": "^5.2.2", - "vite": "^5.1.6", - "vite-plugin-electron": "^0.28.6", - "vite-plugin-electron-renderer": "^0.14.5", - "vitest": "^4.0.16" + "jsdom": "^29.0.2", + "lint-staged": "^16.4.0", + "postcss": "^8.5.10", + "tailwindcss": "^3.4.19", + "terser": "^5.46.1", + "typescript": "^5.9.3", + "vite": "^5.4.21", + "vite-plugin-electron": "^0.28.8", + "vite-plugin-electron-renderer": "^0.14.6", + "vitest": "^4.1.4" }, "main": "dist-electron/main.js", "lint-staged": { From a1762b26912ff4d17a23185f8c084fd642bab9bf Mon Sep 17 00:00:00 2001 From: FabLrc Date: Wed, 8 Apr 2026 14:04:17 +0200 Subject: [PATCH 115/152] Update French translations for dialogs, editor, and settings --- src/i18n/locales/fr/dialogs.json | 2 +- src/i18n/locales/fr/editor.json | 6 ++++++ src/i18n/locales/fr/settings.json | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/fr/dialogs.json b/src/i18n/locales/fr/dialogs.json index fc32e6b..dbaae38 100644 --- a/src/i18n/locales/fr/dialogs.json +++ b/src/i18n/locales/fr/dialogs.json @@ -29,7 +29,7 @@ "description": "Comprendre comment supprimer les parties indésirables de votre vidéo.", "explanationBefore": "L'outil Coupe fonctionne en définissant les segments que vous souhaitez", "remove": "supprimer", - "explanationMiddle": " — tout élément", + "explanationMiddle": " — tout ce qui est", "covered": "couvert", "explanationAfter": "par un segment de coupe rouge sera coupé lors de l'export.", "visualExample": "Exemple visuel", diff --git a/src/i18n/locales/fr/editor.json b/src/i18n/locales/fr/editor.json index 779bcd7..462f49f 100644 --- a/src/i18n/locales/fr/editor.json +++ b/src/i18n/locales/fr/editor.json @@ -1,4 +1,10 @@ { + "newRecording": { + "title": "Retour à l'enregistreur", + "description": "Votre session actuelle a été enregistrée.", + "cancel": "Annuler", + "confirm": "Confirmer" + }, "errors": { "noVideoLoaded": "Aucune vidéo chargée", "videoNotReady": "Vidéo non prête", diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index ae98a59..e37f67c 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -13,7 +13,9 @@ "speed": { "playbackSpeed": "Vitesse de lecture", "selectRegion": "Sélectionnez une région de vitesse à ajuster", - "deleteRegion": "Supprimer la région de vitesse" + "deleteRegion": "Supprimer la région de vitesse", + "customPlaybackSpeed": "Vitesse de lecture personnalisée", + "maxSpeedError": "La vitesse ne peut pas dépasser 16×" }, "trim": { "deleteRegion": "Supprimer la région de coupe" @@ -24,7 +26,8 @@ "selectPreset": "Choisir un préréglage", "pictureInPicture": "Incrustation d'image", "verticalStack": "Empilement vertical", - "webcamShape": "Forme de la caméra" + "webcamShape": "Forme de la caméra", + "webcamSize": "Taille de la caméra" }, "effects": { "title": "Effets vidéo", From 9d365ca406d88c6146896076346ed6d5fd1d648d Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 12:48:29 +0200 Subject: [PATCH 116/152] fix: Update French translations for editor, launch, and settings --- src/i18n/locales/fr/editor.json | 2 ++ src/i18n/locales/fr/launch.json | 8 +++++++- src/i18n/locales/fr/settings.json | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/i18n/locales/fr/editor.json b/src/i18n/locales/fr/editor.json index 462f49f..03596bd 100644 --- a/src/i18n/locales/fr/editor.json +++ b/src/i18n/locales/fr/editor.json @@ -36,6 +36,8 @@ "systemAudioUnavailable": "Audio système non disponible. Enregistrement sans audio système.", "microphoneDenied": "Accès au microphone refusé. L'enregistrement continuera sans audio.", "cameraDenied": "Accès à la caméra refusé. L'enregistrement continuera sans webcam.", + "cameraDisconnected": "Webcam déconnectée.", + "cameraNotFound": "Caméra introuvable.", "permissionDenied": "Permission d'enregistrement refusée. Veuillez autoriser l'enregistrement d'écran." } } diff --git a/src/i18n/locales/fr/launch.json b/src/i18n/locales/fr/launch.json index f4bfb27..55521cb 100644 --- a/src/i18n/locales/fr/launch.json +++ b/src/i18n/locales/fr/launch.json @@ -33,5 +33,11 @@ "recording": { "selectSource": "Veuillez sélectionner une source à enregistrer" }, - "language": "Langue" + "language": "Langue", + "systemLanguagePrompt": { + "title": "Utiliser la langue de votre système ?", + "description": "Nous avons détecté {{language}} comme langue système. Voulez-vous passer OpenScreen en {{language}} ?", + "switch": "Passer en {{language}}", + "keepDefault": "Conserver la langue actuelle" + } } diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index e37f67c..0dff11f 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -8,6 +8,13 @@ "manual": "Manuel", "auto": "Auto", "autoDescription": "La caméra suit la position du curseur enregistré" + }, + "speed": { + "title": "Vitesse du zoom", + "instant": "Instantané", + "fast": "Rapide", + "smooth": "Fluide", + "lazy": "Lent" } }, "speed": { @@ -26,6 +33,7 @@ "selectPreset": "Choisir un préréglage", "pictureInPicture": "Incrustation d'image", "verticalStack": "Empilement vertical", + "dualFrame": "Double cadre", "webcamShape": "Forme de la caméra", "webcamSize": "Taille de la caméra" }, From 41a26f3e660b21dd9b40fdca1b337c3a48855fd7 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 14:04:34 +0200 Subject: [PATCH 117/152] fix: upgrade vite to 6.x to satisfy vitest 4.x peer dependency vitest ^4.1.4 requires vite ^6+, which conflicted with the pinned vite 5.4.21 and caused npm ci to fail with an inconsistent lockfile. Also bumps vite-plugin-electron to 0.29.1. --- package-lock.json | 366 +++++++++++++++++++++++++++++----------------- package.json | 4 +- 2 files changed, 235 insertions(+), 135 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e1b349..a92d0e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,8 +74,8 @@ "tailwindcss": "^3.4.19", "terser": "^5.46.1", "typescript": "^5.9.3", - "vite": "^5.4.21", - "vite-plugin-electron": "^0.28.8", + "vite": "^6.4.2", + "vite-plugin-electron": "^0.29.1", "vite-plugin-electron-renderer": "^0.14.6", "vitest": "^4.1.4" }, @@ -1690,9 +1690,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -1703,13 +1703,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -1720,13 +1720,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -1737,13 +1737,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -1754,13 +1754,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -1771,13 +1771,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -1788,13 +1788,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -1805,13 +1805,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -1822,13 +1822,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -1839,13 +1839,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -1856,13 +1856,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -1873,13 +1873,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -1890,13 +1890,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -1907,13 +1907,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -1924,13 +1924,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -1941,13 +1941,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -1958,13 +1958,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -1975,13 +1975,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -1992,13 +2009,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -2009,13 +2043,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -2026,13 +2077,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -2043,13 +2094,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -2060,13 +2111,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -2077,7 +2128,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@exodus/bytes": { @@ -8270,9 +8321,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8280,32 +8331,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -15060,21 +15114,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -15083,19 +15140,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -15116,13 +15179,19 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-plugin-electron": { - "version": "0.28.8", - "resolved": "https://registry.npmjs.org/vite-plugin-electron/-/vite-plugin-electron-0.28.8.tgz", - "integrity": "sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==", + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/vite-plugin-electron/-/vite-plugin-electron-0.29.1.tgz", + "integrity": "sha512-AejNed5BgHFnuw8h5puTa61C6vdP4ydbsbo/uVjH1fTdHAlCDz1+o6pDQ/scQj1udDrGvH01+vTbzQh/vMnR9w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -15141,6 +15210,37 @@ "dev": true, "license": "MIT" }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", diff --git a/package.json b/package.json index 2d77400..d0685b5 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,8 @@ "tailwindcss": "^3.4.19", "terser": "^5.46.1", "typescript": "^5.9.3", - "vite": "^5.4.21", - "vite-plugin-electron": "^0.28.8", + "vite": "^6.4.2", + "vite-plugin-electron": "^0.29.1", "vite-plugin-electron-renderer": "^0.14.6", "vitest": "^4.1.4" }, From 018ba08eb964e28c6e822e6971f1f15460d2b5d1 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 14:07:23 +0200 Subject: [PATCH 118/152] fix(security): remove unused electron-icon-builder and electron-rebuild Both packages were listed as devDependencies but not referenced in any scripts or source files. Removing them eliminates all 22 npm audit vulnerabilities (2 critical, 5 high, 13 moderate, 2 low) introduced by their unmaintained transitive dependency chain (phantomjs-prebuilt, request, tar, etc.). --- package-lock.json | 3299 +-------------------------------------------- package.json | 2 - 2 files changed, 5 insertions(+), 3296 deletions(-) diff --git a/package-lock.json b/package-lock.json index a92d0e5..666881f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,8 +64,6 @@ "autoprefixer": "^10.5.0", "electron": "^41.2.1", "electron-builder": "^26.8.1", - "electron-icon-builder": "^2.0.1", - "electron-rebuild": "^3.2.9", "fast-check": "^4.7.0", "husky": "^9.1.7", "jsdom": "^29.0.2", @@ -2206,13 +2204,6 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true, - "license": "MIT" - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2339,512 +2330,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/@jimp/bmp": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.13.tgz", - "integrity": "sha512-9edAxu7N2FX7vzkdl5Jo1BbACfycUtBQX+XBMcHA2bk62P8R0otgkHg798frgAk/WxQIzwxqOH6wMiCwrlAzdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "bmp-js": "^0.1.0" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/core": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.13.tgz", - "integrity": "sha512-qXpA1tzTnlkTku9yqtuRtS/wVntvE6f3m3GNxdTdtmc+O+Wcg9Xo2ABPMh7Nc0AHbMKzwvwgB2JnjZmlmJEObg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^16.5.4", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - } - }, - "node_modules/@jimp/core/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/@jimp/custom": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.13.tgz", - "integrity": "sha512-LTATglVUPGkPf15zX1wTMlZ0+AU7cGEGF6ekVF1crA8eHUWsGjrYTB+Ht4E3HTrCok8weQG+K01rJndCp/l4XA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.13" - } - }, - "node_modules/@jimp/gif": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.13.tgz", - "integrity": "sha512-yFAMZGv3o+YcjXilMWWwS/bv1iSqykFahFMSO169uVMtfQVfa90kt4/kDwrXNR6Q9i6VHpFiGZMlF2UnHClBvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/jpeg": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.16.13.tgz", - "integrity": "sha512-BJHlDxzTlCqP2ThqP8J0eDrbBfod7npWCbJAcfkKqdQuFk0zBPaZ6KKaQKyKxmWJ87Z6ohANZoMKEbtvrwz1AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "jpeg-js": "^0.4.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.13.tgz", - "integrity": "sha512-8Z1k96ZFxlhK2bgrY1JNWNwvaBeI/bciLM0yDOni2+aZwfIIiC7Y6PeWHTAvjHNjphz+XCt01WQmOYWCn0ML6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.13.tgz", - "integrity": "sha512-PvLrfa8vkej3qinlebyhLpksJgCF5aiysDMSVhOZqwH5nQLLtDE9WYbnsofGw4r0VVpyw3H/ANCIzYTyCtP9Cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.13.tgz", - "integrity": "sha512-RNave7EFgZrb5V5EpdvJGAEHMnDAJuwv05hKscNfIYxf0kR3KhViBTDy+MoTnMlIvaKFULfwIgaZWzyhuINMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.13.tgz", - "integrity": "sha512-xW+9BtEvoIkkH/Wde9ql4nAFbYLkVINhpgAE7VcBUsuuB34WUbcBl/taOuUYQrPEFQJ4jfXiAJZ2H/rvKjCVnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "tinycolor2": "^1.4.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.13.tgz", - "integrity": "sha512-QayTXw4tXMwU6q6acNTQrTTFTXpNRBe+MgTGMDU0lk+23PjlFCO/9sacflelG8lsp7vNHhAxFeHptDMAksEYzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.13.tgz", - "integrity": "sha512-BSsP71GTNaqWRcvkbWuIVH+zK7b3TSNebbhDkFK0fVaUTzHuKMS/mgY4hDZIEVt7Rf5FjadAYtsujHN9w0iSYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.13.tgz", - "integrity": "sha512-WEl2tPVYwzYL8OKme6Go2xqiWgKsgxlMwyHabdAU4tXaRwOCnOI7v4021gCcBb9zn/oWwguHuKHmK30Fw2Z/PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.13.tgz", - "integrity": "sha512-qt9WKq8vWrcjySa9DyQ0x/RBMHQeiVjdVSY1SJsMjssPUf0pS74qorcuAkGi89biN3YoGUgPkpqECnAWnYwgGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.13.tgz", - "integrity": "sha512-5/N3yJggbWQTlGZHQYJPmQXEwR52qaXjEzkp1yRBbtdaekXE3BG/suo0fqeoV/csf8ooI78sJzYmIrxNoWVtgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.13.tgz", - "integrity": "sha512-2rZmTdFbT/cF9lEZIkXCYO0TsT114Q27AX5IAo0Sju6jVQbvIk1dFUTnwLDadTo8wkJlFzGqMQ24Cs8cHWOliA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.13.tgz", - "integrity": "sha512-EmcgAA74FTc5u7Z+hUO/sRjWwfPPLuOQP5O64x5g4j0T12Bd29IgsYZxoutZo/rb3579+JNa/3wsSEmyVv1EpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-rotate": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-gaussian": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.13.tgz", - "integrity": "sha512-A1XKfGQD0iDdIiKqFYi8nZMv4dDVYdxbrmgR7y/CzUHhSYdcmoljLIIsZZM3Iks/Wa353W3vtvkWLuDbQbch1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-invert": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.13.tgz", - "integrity": "sha512-xFMrIn7czEZbdbMzZWuaZFnlLGJDVJ82y5vlsKsXRTG2kcxRsMPXvZRWHV57nSs1YFsNqXSbrC8B98n0E32njQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.13.tgz", - "integrity": "sha512-wLRYKVBXql2GAYgt6FkTnCfE+q5NomM7Dlh0oIPGAoMBWDyTx0eYutRK6PlUrRK2yMHuroAJCglICTbxqGzowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-normalize": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.13.tgz", - "integrity": "sha512-3tfad0n9soRna4IfW9NzQdQ2Z3ijkmo21DREHbE6CGcMIxOSvfRdSvf1qQPApxjTSo8LTU4MCi/fidx/NZ0GqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.13.tgz", - "integrity": "sha512-0m6i3p01PGRkGAK9r53hDYrkyMq+tlhLOIbsSTmZyh6HLshUKlTB7eXskF5OpVd5ZUHoltlNc6R+ggvKIzxRFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "load-bmfont": "^1.4.0" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.13.tgz", - "integrity": "sha512-qoqtN8LDknm3fJm9nuPygJv30O3vGhSBD2TxrsCnhtOsxKAqVPJtFVdGd/qVuZ8nqQANQmTlfqTiK9mVWQ7MiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.13.tgz", - "integrity": "sha512-Ev+Jjmj1nHYw897z9C3R9dYsPv7S2/nxdgfFb/h8hOwK0Ovd1k/+yYS46A0uj/JCKK0pQk8wOslYBkPwdnLorw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-scale": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.13.tgz", - "integrity": "sha512-05POQaEJVucjTiSGMoH68ZiELc7QqpIpuQlZ2JBbhCV+WCbPFUBcGSmE7w4Jd0E2GvCho/NoMODLwgcVGQA97A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-shadow": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.13.tgz", - "integrity": "sha512-nmu5VSZ9hsB1JchTKhnnCY+paRBnwzSyK5fhkhtQHHoFD5ArBQ/5wU8y6tCr7k/GQhhGq1OrixsECeMjPoc8Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blur": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.13.tgz", - "integrity": "sha512-+3zArBH0OE3Rhjm4HyAokMsZlIq5gpQec33CncyoSwxtRBM2WAhUVmCUKuBo+Lr/2/4ISoY4BWpHKhMLDix6cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-color": ">=0.8.0", - "@jimp/plugin-resize": ">=0.8.0" - } - }, - "node_modules/@jimp/plugins": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.13.tgz", - "integrity": "sha512-CJLdqODEhEVs4MgWCxpWL5l95sCBlkuSLz65cxEm56X5akIsn4LOlwnKoSEZioYcZUBvHhCheH67AyPTudfnQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.13", - "@jimp/plugin-blur": "^0.16.13", - "@jimp/plugin-circle": "^0.16.13", - "@jimp/plugin-color": "^0.16.13", - "@jimp/plugin-contain": "^0.16.13", - "@jimp/plugin-cover": "^0.16.13", - "@jimp/plugin-crop": "^0.16.13", - "@jimp/plugin-displace": "^0.16.13", - "@jimp/plugin-dither": "^0.16.13", - "@jimp/plugin-fisheye": "^0.16.13", - "@jimp/plugin-flip": "^0.16.13", - "@jimp/plugin-gaussian": "^0.16.13", - "@jimp/plugin-invert": "^0.16.13", - "@jimp/plugin-mask": "^0.16.13", - "@jimp/plugin-normalize": "^0.16.13", - "@jimp/plugin-print": "^0.16.13", - "@jimp/plugin-resize": "^0.16.13", - "@jimp/plugin-rotate": "^0.16.13", - "@jimp/plugin-scale": "^0.16.13", - "@jimp/plugin-shadow": "^0.16.13", - "@jimp/plugin-threshold": "^0.16.13", - "timm": "^1.6.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/png": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.13.tgz", - "integrity": "sha512-8cGqINvbWJf1G0Her9zbq9I80roEX0A+U45xFby3tDWfzn+Zz8XKDF1Nv9VUwVx0N3zpcG1RPs9hfheG4Cq2kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.13", - "pngjs": "^3.3.3" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/png/node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@jimp/tiff": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.13.tgz", - "integrity": "sha512-oJY8d9u95SwW00VPHuCNxPap6Q1+E/xM5QThb9Hu+P6EGuu6lIeLaNBMmFZyblwFbwrH+WBOZlvIzDhi4Dm/6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/types": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.13.tgz", - "integrity": "sha512-mC0yVNUobFDjoYLg4hoUwzMKgNlxynzwt3cDXzumGvRJ7Kb8qQGOWJQjQFo5OxmGExqzPphkirdbBF88RVLBCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.13", - "@jimp/gif": "^0.16.13", - "@jimp/jpeg": "^0.16.13", - "@jimp/png": "^0.16.13", - "@jimp/tiff": "^0.16.13", - "timm": "^1.6.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/utils": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.16.13.tgz", - "integrity": "sha512-VyCpkZzFTHXtKgVO35iKN0sYR10psGpV6SkcSeV4oF7eSYlR8Bl6aQLCzVeFjvESF7mxTmIiI3/XrMobVrtxDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3111,35 +2596,6 @@ "node": ">= 14" } }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@oxc-project/types": { "version": "0.126.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", @@ -5126,23 +4582,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5719,26 +5158,6 @@ "dev": true, "license": "MIT" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -5752,46 +5171,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -5861,13 +5240,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "dev": true, - "license": "MIT" - }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -6133,28 +5505,6 @@ "node": ">=18" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "dev": true, - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -6168,100 +5518,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/args": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.3.tgz", - "integrity": "sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/args/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/args/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/args/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/args/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/args/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/args/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/args/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -6284,32 +5540,13 @@ "dequal": "^2.0.3" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.8" } @@ -6406,23 +5643,6 @@ "postcss": "^8.1.0" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6464,16 +5684,6 @@ "node": ">=6.0.0" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -6508,13 +5718,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", - "dev": true, - "license": "MIT" - }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -6737,93 +5940,6 @@ "node": ">= 10.0.0" } }, - "node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -6883,16 +5999,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "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", @@ -6923,23 +6029,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/centra": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", - "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -7003,16 +6092,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", @@ -7048,16 +6127,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -7149,16 +6218,6 @@ "node": ">=6" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7179,16 +6238,6 @@ "dev": true, "license": "MIT" }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -7236,62 +6285,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true, - "license": "ISC" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -7304,7 +6297,8 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/crc": { "version": "3.8.0", @@ -7381,19 +6375,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-urls": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", @@ -7426,16 +6407,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -7533,29 +6504,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7566,13 +6514,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -7648,19 +6589,6 @@ "node": "*" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -7768,12 +6696,6 @@ "license": "MIT", "peer": true }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "dev": true - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -7830,17 +6752,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -7953,24 +6864,6 @@ "node": ">= 10.0.0" } }, - "node_modules/electron-icon-builder": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/electron-icon-builder/-/electron-icon-builder-2.0.1.tgz", - "integrity": "sha512-rg9BxW2kJi3TXsMFFNXWXrwQEd5dzXmeD+w7Pj3k3z7aYRePLxE89qU4lvL/rK1X/NTY5KDn3+Dbgm1TU2dGXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "args": "^5.0.1", - "icon-gen": "^2.0.0", - "jimp": "^0.16.1" - }, - "bin": { - "electron-icon-builder": "index.js" - }, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/electron-publish": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", @@ -8026,74 +6919,6 @@ "node": ">= 10.0.0" } }, - "node_modules/electron-rebuild": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.9.tgz", - "integrity": "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw==", - "deprecated": "Please use @electron/rebuild moving forward. There is no API change, just a package name change", - "dev": true, - "license": "MIT", - "dependencies": { - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "fs-extra": "^10.0.0", - "got": "^11.7.0", - "lzma-native": "^8.0.5", - "node-abi": "^3.0.0", - "node-api-version": "^0.1.4", - "node-gyp": "^9.0.0", - "ora": "^5.1.0", - "semver": "^7.3.5", - "tar": "^6.0.5", - "yargs": "^17.0.1" - }, - "bin": { - "electron-rebuild": "lib/src/cli.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/electron-rebuild/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-rebuild/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-rebuild/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.340", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", @@ -8242,16 +7067,6 @@ "dev": true, "license": "MIT" }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -8313,13 +7128,6 @@ "license": "MIT", "optional": true }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true, - "license": "MIT" - }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -8396,38 +7204,12 @@ "@types/estree": "^1.0.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "dev": true - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -8445,13 +7227,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -8568,34 +7343,6 @@ "pend": "~1.2.0" } }, - "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/file-url": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/file-url/-/file-url-2.0.2.tgz", - "integrity": "sha512-x3989K8a1jM6vulMigE8VngH7C5nci0Ks5d9kVjUXmNF28gmiZUNujk5HjwaS8dAzN2QmUfX56riJKgN00dNRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/filelist": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", @@ -8643,27 +7390,6 @@ "integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==", "license": "MIT" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -8681,16 +7407,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -8764,32 +7480,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8820,34 +7510,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8943,16 +7605,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/gif.js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/gif.js/-/gif.js-0.2.0.tgz", @@ -8968,17 +7620,6 @@ "js-binary-schema-parser": "^2.0.3" } }, - "node_modules/gifwrap": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", - "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9037,17 +7678,6 @@ "node": "*" } }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -9085,27 +7715,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -9157,31 +7766,6 @@ "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9234,27 +7818,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha512-jZ38TU/EBiGKrmyTNNZgnvCZHNowiRI4+w/I9noMlekHTZH3KyGgvJLmhSgykeAQ9j2SYPDosM0Bg3wHfzibAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -9300,37 +7863,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -9345,30 +7877,6 @@ "node": ">=10.19.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -9385,47 +7893,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/icon-gen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icon-gen/-/icon-gen-2.1.0.tgz", - "integrity": "sha512-rqIVvq9MJ8X7wnJW0NO8Eau/+5RWV7AH6L5vEt/U5Ajv5WefdDNDxGwJhGokyHuyBWeX7JqRMQ03tG0gAco4Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^6.2.0", - "del": "^6.0.0", - "mkdirp": "^1.0.4", - "pngjs": "^6.0.0", - "svg2png": "4.1.1", - "uuid": "^8.3.1" - }, - "bin": { - "icon-gen": "dist/bin/index.js" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/icon-gen/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/icon-gen/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -9478,33 +7945,6 @@ ], "license": "BSD-3-Clause" }, - "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/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "16.9.1" - } - }, - "node_modules/image-q/node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "dev": true, - "license": "MIT" - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9525,13 +7965,6 @@ "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true, - "license": "ISC" - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -9551,16 +7984,6 @@ "dev": true, "license": "ISC" }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -9571,13 +7994,6 @@ "node": ">= 12" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -9624,13 +8040,6 @@ "node": ">=8" } }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9653,13 +8062,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9669,26 +8071,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -9696,23 +8078,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -9726,20 +8091,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isbinaryfile": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", @@ -9766,13 +8117,6 @@ "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", "license": "MIT" }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -9807,20 +8151,6 @@ "node": ">=10" } }, - "node_modules/jimp": { - "version": "0.16.13", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.13.tgz", - "integrity": "sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.13", - "@jimp/plugins": "^0.16.13", - "@jimp/types": "^0.16.13", - "regenerator-runtime": "^0.13.3" - } - }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -9831,13 +8161,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/js-binary-schema-parser": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", @@ -9863,13 +8186,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdom": { "version": "29.0.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", @@ -9954,13 +8270,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9973,7 +8282,8 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/json5": { "version": "2.2.3", @@ -9998,54 +8308,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jsprim/node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/jsprim/node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10056,16 +8318,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", @@ -10073,29 +8325,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -10592,77 +8821,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/load-bmfont": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", - "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal": "0.0.1", - "mime": "^1.3.4", - "parse-bmfont-ascii": "^1.0.3", - "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.4", - "phin": "^3.7.1", - "xhr": "^2.0.1", - "xtend": "^4.0.0" - } - }, - "node_modules/load-bmfont/node_modules/buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/load-bmfont/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-bmfont/node_modules/phin": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", - "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "dependencies": { - "centra": "^2.7.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -10929,32 +9087,6 @@ "lz-string": "bin/bin.js" } }, - "node_modules/lzma-native": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.6.tgz", - "integrity": "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^3.1.0", - "node-gyp-build": "^4.2.1", - "readable-stream": "^3.6.0" - }, - "bin": { - "lzmajs": "bin/lzmajs" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/lzma-native/node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true, - "license": "MIT" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -10965,57 +9097,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -11160,16 +9241,6 @@ "node": ">=4" } }, - "node_modules/min-document": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", - "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -11239,63 +9310,6 @@ "node": ">=8" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -11374,46 +9388,6 @@ "node": ">=8" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/motion": { "version": "12.38.0", "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", @@ -11464,16 +9438,6 @@ "node": ">=20.8.1" } }, - "node_modules/mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -11519,29 +9483,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.78.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", - "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -11550,54 +9491,6 @@ "license": "MIT", "optional": true }, - "node_modules/node-api-version": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.1.4.tgz", - "integrity": "sha512-KGXihXdUChwJAOHO53bv9/vXcLmdUsZ6jIptbvYvkpKfth+r7jw44JkVxQFA3kX5nQjzjmGu1uAu/xNNLNlI5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -11605,52 +9498,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -11673,43 +9520,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11763,13 +9573,6 @@ ], "license": "MIT" }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true, - "license": "MIT" - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11820,19 +9623,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -11859,22 +9649,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -11882,58 +9656,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", - "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.5.0" - } - }, - "node_modules/parse-headers": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", - "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-svg-path": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", @@ -12003,16 +9725,6 @@ "dev": true, "license": "ISC" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -12035,20 +9747,6 @@ "url": "https://github.com/sponsors/jet2jet" } }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -12056,134 +9754,6 @@ "dev": true, "license": "MIT" }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "license": "MIT" - }, - "node_modules/phantomjs-prebuilt": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", - "integrity": "sha512-PIiRzBhW85xco2fuj41FmsyuYHKjKuXWmhjy3A/Y+CMpN/63TV+s9uzfVhsUwFe0G77xWtHBG8xmXf5BqEUEuQ==", - "deprecated": "this package is now deprecated", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "es6-promise": "^4.0.3", - "extract-zip": "^1.6.5", - "fs-extra": "^1.0.0", - "hasha": "^2.2.0", - "kew": "^0.7.0", - "progress": "^1.1.8", - "request": "^2.81.0", - "request-progress": "^2.0.1", - "which": "^1.2.10" - }, - "bin": { - "phantomjs": "bin/phantomjs" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/extract-zip": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", - "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/phantomjs-prebuilt/node_modules/progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/phantomjs-prebuilt/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/phin": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", - "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -12211,29 +9781,6 @@ "node": ">=0.10.0" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -12243,29 +9790,6 @@ "node": ">= 6" } }, - "node_modules/pixelmatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", - "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^3.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/pixi-filters": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.1.5.tgz", @@ -12363,23 +9887,6 @@ "node": ">=10.4.0" } }, - "node_modules/pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true, - "license": "MIT" - }, - "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, "node_modules/postcss": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", @@ -12614,23 +10121,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -12641,13 +10131,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, - "license": "ISC" - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -12692,19 +10175,6 @@ "dev": true, "license": "ISC" }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -12988,77 +10458,6 @@ "pify": "^2.3.0" } }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13074,65 +10473,6 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13159,92 +10499,6 @@ "node": ">=8" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true, - "license": "MIT" - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha512-dxdraeZVUNEn9AvLrxkgB2k6buTlym71dJk1fk4v8j3Ou3RKNm07BcgbHdj2lLgYGfqX71F+awb1MR+tWPFJzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", - "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13265,13 +10519,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true, - "license": "ISC" - }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", @@ -13384,23 +10631,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -13648,13 +10878,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -13801,16 +11024,6 @@ "node": ">=18" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -13853,21 +11066,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -13908,42 +11106,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -13952,58 +11114,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ssri/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -14106,19 +11216,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -14132,24 +11229,6 @@ "node": ">=8" } }, - "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -14219,155 +11298,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg2png": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/svg2png/-/svg2png-4.1.1.tgz", - "integrity": "sha512-9tOp9Ugjlunuf1ugqkhiYboTmTaTI7p48dz5ZjNA5NQJ5xS1NLTZZ1tF8vkJOIBb/ZwxGJsKZvRWqVpo4q9z9Q==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "file-url": "^2.0.0", - "phantomjs-prebuilt": "^2.1.14", - "pn": "^1.0.0", - "yargs": "^6.5.0" - }, - "bin": { - "svg2png": "bin/svg2png-cli.js" - } - }, - "node_modules/svg2png/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/svg2png/node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true, - "license": "ISC" - }, - "node_modules/svg2png/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svg2png/node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/svg2png/node_modules/yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha512-6/QWTdisjnu5UHUzQGst+UOEuEVwIzFVGBjq3jMTFNs5WJQsH/X6nMURSaScIdF5txylr1Ao9bvbWiKi2yXbwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^4.2.0" - } - }, - "node_modules/svg2png/node_modules/yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha512-+QQWqC2xeL0N5/TE+TY6OGEqyNRM+g2/r712PDNYgiCdXYCApXf1vzfmDSLBxfGRwV+moTq/V8FnMI24JCm2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^3.0.0" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -14440,24 +11370,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -14598,23 +11510,6 @@ "node": ">=0.8" } }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/timm": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", - "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", - "dev": true, - "license": "MIT" - }, "node_modules/tiny-async-pool": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", @@ -14651,13 +11546,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -14775,24 +11663,6 @@ "node": ">=8.0" } }, - "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -14802,20 +11672,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tr46": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", @@ -14851,33 +11707,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -14909,32 +11738,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -15057,16 +11860,6 @@ "dev": true, "license": "(WTFPL OR MIT)" }, - "node_modules/utif": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", - "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pako": "^1.0.5" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15086,17 +11879,6 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/verror": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", @@ -15532,13 +12314,6 @@ "node": ">= 8" } }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true, - "license": "ISC" - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -15556,16 +12331,6 @@ "node": ">=8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -15631,19 +12396,6 @@ } } }, - "node_modules/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -15654,37 +12406,6 @@ "node": ">=18" } }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -15702,16 +12423,6 @@ "dev": true, "license": "MIT" }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index d0685b5..d126365 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,6 @@ "autoprefixer": "^10.5.0", "electron": "^41.2.1", "electron-builder": "^26.8.1", - "electron-icon-builder": "^2.0.1", - "electron-rebuild": "^3.2.9", "fast-check": "^4.7.0", "husky": "^9.1.7", "jsdom": "^29.0.2", From b472c768ce1e229287e7b5f1ee3bda1c2ec5faee Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 14:11:31 +0200 Subject: [PATCH 119/152] =?UTF-8?q?style:=20migrate=20biome=20config=20to?= =?UTF-8?q?=202.4.12=20and=20fix=20formatting=20(CRLF=20=E2=86=92=20LF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biome.json | 2 +- src/components/ui/accordion.tsx | 2 +- src/components/ui/card.tsx | 2 +- src/components/ui/dialog.tsx | 12 ++++++------ src/components/ui/dropdown-menu.tsx | 14 +++++++------- src/components/ui/popover.tsx | 2 +- src/components/ui/select.tsx | 12 ++++++------ src/components/ui/tabs.tsx | 2 +- src/components/ui/tooltip.tsx | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/biome.json b/biome.json index c4c22f6..517be72 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "ignoreUnknown": false, "includes": ["**", "!**/*.css"] }, "formatter": { diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index 85336fd..22456b2 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -52,4 +52,4 @@ const AccordionContent = React.forwardRef< )); AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 2935ed4..0480764 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -52,4 +52,4 @@ const CardFooter = React.forwardRef Date: Tue, 21 Apr 2026 18:01:59 +0530 Subject: [PATCH 120/152] Remove unnecessary newline in i18n-check.mjs --- scripts/i18n-check.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index 699ae9e..b37af68 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -5,7 +5,6 @@ * * Usage: node scripts/i18n-check.mjs */ - import fs from "node:fs"; import path from "node:path"; From 659affa88cfee866a91c23387a221460013501d4 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 14:23:22 +0200 Subject: [PATCH 121/152] fix: upgrade vite to 7.x to resolve lockfile/platform issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vitest@4.1.4 requires vite ^6||^7||^8. With vite@6 at project level, npm@10 installs a separate vite@8 for vitest, which pulls in rolldown (native .node bindings) that npm ci cannot install cross-platform due to npm bug #4828. vite@7 avoids rolldown entirely (uses rollup) and npm@10 deduplicates correctly with the project-level vite@7. Also adds esbuild@^0.27.0 explicitly (required by vite-plugin-electron-renderer) and aligns with vite@7's own esbuild@^0.27.0 so no duplicate installs. - vite: ^6.4.2 → ^7.3.2 - @vitejs/plugin-react: ^4.7.0 → ^5.2.0 (adds vite@7/8 support) - esbuild: ^0.27.0 added explicitly - vite.config.ts: manualChunks converted to function form (rollup compat) --- package-lock.json | 1586 ++++----------------------------------------- package.json | 5 +- vite.config.ts | 14 +- 3 files changed, 156 insertions(+), 1449 deletions(-) diff --git a/package-lock.json b/package-lock.json index 666881f..d0bdbbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,12 +58,13 @@ "@types/node": "^25.6.0", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^5.2.0", "@vitest/browser": "^4.1.4", "@vitest/browser-playwright": "^4.1.4", "autoprefixer": "^10.5.0", "electron": "^41.2.1", "electron-builder": "^26.8.1", + "esbuild": "^0.27.0", "fast-check": "^4.7.0", "husky": "^9.1.7", "jsdom": "^29.0.2", @@ -72,7 +73,7 @@ "tailwindcss": "^3.4.19", "terser": "^5.46.1", "typescript": "^5.9.3", - "vite": "^6.4.2", + "vite": "^7.3.2", "vite-plugin-electron": "^0.29.1", "vite-plugin-electron-renderer": "^0.14.6", "vitest": "^4.1.4" @@ -507,125 +508,6 @@ "@biomejs/cli-win32-x64": "2.4.12" } }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", - "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", - "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", - "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", - "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", - "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", - "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", - "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, "node_modules/@biomejs/cli-win32-x64": { "version": "2.4.12", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", @@ -1653,44 +1535,10 @@ "node": ">= 10.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -1705,9 +1553,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -1722,9 +1570,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -1739,9 +1587,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -1756,9 +1604,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -1773,9 +1621,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -1790,9 +1638,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -1807,9 +1655,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -1824,9 +1672,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -1841,9 +1689,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -1858,9 +1706,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -1875,9 +1723,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -1892,9 +1740,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -1909,9 +1757,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -1926,9 +1774,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -1943,9 +1791,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -1960,9 +1808,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -1977,9 +1825,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -1994,9 +1842,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -2011,9 +1859,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -2028,9 +1876,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -2045,9 +1893,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -2062,9 +1910,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -2079,9 +1927,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -2096,9 +1944,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -2113,9 +1961,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -2465,25 +2313,6 @@ "node": ">= 10.0.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2596,16 +2425,6 @@ "node": ">= 14" } }, - "node_modules/@oxc-project/types": { - "version": "0.126.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", - "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/@pixi/color": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", @@ -3845,592 +3664,13 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz", - "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz", - "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz", - "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz", - "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz", - "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz", - "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz", - "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz", - "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz", - "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz", - "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", @@ -4582,17 +3822,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -4787,18 +4016,6 @@ "undici-types": "~7.19.0" } }, - "node_modules/@types/plist": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", - "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "xmlbuilder": ">=11.0.1" - } - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -4837,14 +4054,6 @@ "@types/node": "*" } }, - "node_modules/@types/verror": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", - "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -4919,24 +4128,24 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "react-refresh": "^0.18.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@vitest/browser": { @@ -5540,17 +4749,6 @@ "dequal": "^2.0.3" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -5561,17 +4759,6 @@ "node": ">=12" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -6153,24 +5340,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6292,25 +5461,6 @@ "dev": true, "license": "MIT" }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.1.0" - } - }, "node_modules/cross-dirname": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", @@ -6650,33 +5800,6 @@ "node": ">= 10.0.0" } }, - "node_modules/dmg-license": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", - "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "@types/plist": "^3.0.1", - "@types/verror": "^1.10.3", - "ajv": "^6.10.0", - "crc": "^3.8.0", - "iconv-corefoundation": "^1.1.7", - "plist": "^3.0.4", - "smart-buffer": "^4.0.2", - "verror": "^1.10.0" - }, - "bin": { - "dmg-license": "bin/dmg-license.js" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dnd-timeline": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dnd-timeline/-/dnd-timeline-2.4.0.tgz", @@ -7129,9 +6252,9 @@ "optional": true }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7142,32 +6265,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escalade": { @@ -7248,17 +6371,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "optional": true - }, "node_modules/fast-check": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", @@ -7487,20 +6599,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7893,24 +6991,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8331,6 +7411,8 @@ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", + "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -8355,216 +7437,6 @@ "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", @@ -8578,6 +7450,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -9483,14 +8356,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/node-releases": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -9858,20 +8723,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -10327,9 +9178,9 @@ "license": "MIT" }, "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, "license": "MIT", "engines": { @@ -10650,47 +9501,6 @@ "node": ">=8.0" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz", - "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.126.0", - "@rolldown/pluginutils": "1.0.0-rc.16" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.16", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.16", - "@rolldown/binding-darwin-x64": "1.0.0-rc.16", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.16", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz", - "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", @@ -11024,22 +9834,6 @@ "node": ">=18" } }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -11879,41 +10673,25 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -11922,14 +10700,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -12153,84 +10931,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest/node_modules/vite": { - "version": "8.0.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz", - "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.16", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index d126365..446976c 100644 --- a/package.json +++ b/package.json @@ -82,12 +82,13 @@ "@types/node": "^25.6.0", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^5.2.0", "@vitest/browser": "^4.1.4", "@vitest/browser-playwright": "^4.1.4", "autoprefixer": "^10.5.0", "electron": "^41.2.1", "electron-builder": "^26.8.1", + "esbuild": "^0.27.0", "fast-check": "^4.7.0", "husky": "^9.1.7", "jsdom": "^29.0.2", @@ -96,7 +97,7 @@ "tailwindcss": "^3.4.19", "terser": "^5.46.1", "typescript": "^5.9.3", - "vite": "^6.4.2", + "vite": "^7.3.2", "vite-plugin-electron": "^0.29.1", "vite-plugin-electron-renderer": "^0.14.6", "vitest": "^4.1.4" diff --git a/vite.config.ts b/vite.config.ts index 9a44766..725f24e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -47,10 +47,16 @@ export default defineConfig({ }, rollupOptions: { output: { - manualChunks: { - pixi: ["pixi.js"], - "react-vendor": ["react", "react-dom"], - "video-processing": ["mediabunny", "mp4box", "@fix-webm-duration/fix"], + manualChunks(id) { + if (id.includes("pixi.js") || id.includes("pixi-filters") || id.includes("@pixi/")) + return "pixi"; + if (id.includes("react-dom") || id.includes("/react/")) return "react-vendor"; + if ( + id.includes("mediabunny") || + id.includes("mp4box") || + id.includes("fix-webm-duration") + ) + return "video-processing"; }, }, }, From 7573d8822c8ddd9559c20f2e1a29ab98c4a7e9bd Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 15:00:16 +0200 Subject: [PATCH 122/152] fix: regenerate pack-lock.json --- package-lock.json | 3185 ++++++++++++++++++++++++++------------------- 1 file changed, 1838 insertions(+), 1347 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0bdbbe..4996323 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,16 +209,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.29.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", @@ -253,33 +243,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -425,9 +388,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -508,6 +471,125 @@ "@biomejs/cli-win32-x64": "2.4.12" } }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", + "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", + "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", + "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", + "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", + "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", + "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", + "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@biomejs/cli-win32-x64": { "version": "2.4.12", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", @@ -760,6 +842,13 @@ "node": ">=10.12.0" } }, + "node_modules/@electron/asar/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/@electron/asar/node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -860,16 +949,6 @@ "global-agent": "^3.0.0" } }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@electron/notarize": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", @@ -1025,375 +1104,17 @@ "node": ">=22.12.0" } }, - "node_modules/@electron/rebuild/node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "node_modules/@electron/rebuild/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", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@electron/rebuild/node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@electron/rebuild/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/rebuild/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@electron/rebuild/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@electron/rebuild/node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "semver": "bin/semver.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/rebuild/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@electron/rebuild/node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@electron/rebuild/node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/@electron/rebuild/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@electron/rebuild/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@electron/rebuild/node_modules/node-abi": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", - "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.6.3" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/rebuild/node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/@electron/rebuild/node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/rebuild/node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@electron/rebuild/node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@electron/rebuild/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": ">=10" } }, "node_modules/@electron/universal": { @@ -1415,6 +1136,23 @@ "node": ">=16.4" } }, + "node_modules/@electron/universal/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/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@electron/universal/node_modules/fs-extra": { "version": "11.3.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", @@ -2070,19 +1808,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -2121,22 +1846,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2168,16 +1877,6 @@ "node": ">=18.0.0" } }, - "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2365,44 +2064,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@npmcli/agent/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2410,19 +2071,30 @@ "dev": true, "license": "ISC" }, - "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" + "semver": "^7.3.5" }, "engines": { - "node": ">= 14" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs/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/@pixi/color": { @@ -2551,27 +2223,6 @@ "url": "^0.11.0" } }, - "node_modules/@pixi/utils/node_modules/@types/earcut": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", - "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@pixi/utils/node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "license": "ISC", - "peer": true - }, - "node_modules/@pixi/utils/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT", - "peer": true - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2588,6 +2239,7 @@ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "playwright": "1.59.1" }, @@ -2602,7 +2254,8 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@radix-ui/number": { "version": "1.1.1", @@ -3671,6 +3324,328 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", @@ -3933,16 +3908,17 @@ } }, "node_modules/@types/dom-webcodecs": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.17.tgz", - "integrity": "sha512-IwKW5uKL0Zrv5ccUJpjIlqf7ppk2v29l/ZLQxLlwHxljBfnDD9Gxm+hzMkGM0AOAL/21H0pp7cTUYLiiVUGchA==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", "license": "MIT" }, "node_modules/@types/earcut": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz", - "integrity": "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==", - "license": "MIT" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "license": "MIT", + "peer": true }, "node_modules/@types/estree": { "version": "1.0.8", @@ -3983,9 +3959,9 @@ "license": "MIT" }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", "dev": true, "license": "MIT" }, @@ -4016,6 +3992,18 @@ "undici-types": "~7.19.0" } }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -4054,6 +4042,14 @@ "@types/node": "*" } }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -4149,15 +4145,15 @@ } }, "node_modules/@vitest/browser": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.4.tgz", - "integrity": "sha512-TrNaY/yVOwxtrxNsDUC/wQ56xSwplpytTeRAqF/197xV/ZddxxulBsxR6TrhVMyniJmp9in8d5u0AcDaNRY30w==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.5.tgz", + "integrity": "sha512-iCDGI8c4yg+xmjUg2VsygdAUSIIB4x5Rht/P68OXy1hPELKXHDkzh87lkuTcdYmemRChDkEpB426MmDjzC0ziA==", "dev": true, "license": "MIT", "dependencies": { "@blazediff/core": "1.9.1", - "@vitest/mocker": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/mocker": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", @@ -4168,18 +4164,18 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.4" + "vitest": "4.1.5" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.4.tgz", - "integrity": "sha512-q3PchVhZINX23Pv+RERgAtDlp6wzVkID/smOPnZ5YGWpeWUe3jMNYppeVh15j4il3G7JIJty1d1Kicpm0HSMig==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.5.tgz", + "integrity": "sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/browser": "4.1.4", - "@vitest/mocker": "4.1.4", + "@vitest/browser": "4.1.5", + "@vitest/mocker": "4.1.5", "tinyrainbow": "^3.1.0" }, "funding": { @@ -4187,7 +4183,7 @@ }, "peerDependencies": { "playwright": "*", - "vitest": "4.1.4" + "vitest": "4.1.5" }, "peerDependenciesMeta": { "playwright": { @@ -4195,81 +4191,17 @@ } } }, - "node_modules/@vitest/browser-playwright/node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/browser/node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, "node_modules/@vitest/expect": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", - "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4277,10 +4209,37 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", - "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { @@ -4291,13 +4250,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", - "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.4", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -4305,14 +4264,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", - "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4321,9 +4280,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", - "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -4331,13 +4290,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", - "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", + "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4367,10 +4326,20 @@ "dev": true, "license": "MIT" }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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": { @@ -4380,6 +4349,16 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -4468,6 +4447,18 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/app-builder-bin": { "version": "5.0.0-alpha.12", "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", @@ -4574,16 +4565,6 @@ "semver": "bin/semver.js" } }, - "node_modules/app-builder-lib/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/app-builder-lib/node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -4638,80 +4619,17 @@ "node": ">= 10.0.0" } }, - "node_modules/app-builder-lib/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/app-builder-lib/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/app-builder-lib/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/app-builder-lib/node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/app-builder-lib/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/app-builder-lib/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", - "dependencies": { - "isexe": "^3.1.1" - }, "bin": { - "node-which": "bin/which.js" + "semver": "bin/semver.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/app-builder-lib/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": ">=10" } }, "node_modules/arg": { @@ -4749,6 +4667,17 @@ "dequal": "^2.0.3" } }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -4759,6 +4688,17 @@ "node": ">=12" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -4831,11 +4771,14 @@ } }, "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==", + "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" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -4915,13 +4858,16 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "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": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -5051,16 +4997,6 @@ "node": ">=12.0.0" } }, - "node_modules/builder-util/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/builder-util/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -5076,34 +5012,6 @@ "node": ">=12" } }, - "node_modules/builder-util/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/builder-util/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/builder-util/node_modules/jsonfile": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", @@ -5127,6 +5035,92 @@ "node": ">= 10.0.0" } }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/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/cacache/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -5279,6 +5273,16 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", @@ -5315,16 +5319,19 @@ } }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-spinners": { @@ -5340,6 +5347,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5355,6 +5380,37 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -5461,6 +5517,25 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, "node_modules/cross-dirname": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", @@ -5485,6 +5560,29 @@ "node": ">= 8" } }, + "node_modules/cross-spawn/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/cross-spawn/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/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -5715,6 +5813,13 @@ "p-limit": "^3.1.0 " } }, + "node_modules/dir-compare/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/dir-compare/node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -5800,6 +5905,33 @@ "node": ">= 10.0.0" } }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dnd-timeline": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/dnd-timeline/-/dnd-timeline-2.4.0.tgz", @@ -5863,10 +5995,11 @@ } }, "node_modules/earcut": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", - "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", - "license": "ISC" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC", + "peer": true }, "node_modules/eastasianwidth": { "version": "0.2.0", @@ -5965,9 +6098,9 @@ } }, "node_modules/electron-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6328,10 +6461,11 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT", + "peer": true }, "node_modules/expect-type": { "version": "1.3.0", @@ -6371,6 +6505,17 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, "node_modules/fast-check": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", @@ -6437,9 +6582,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -6455,6 +6600,23 @@ "pend": "~1.2.0" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/filelist": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", @@ -6465,6 +6627,23 @@ "minimatch": "^5.0.1" } }, + "node_modules/filelist/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/filelist/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -6519,6 +6698,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -6592,6 +6784,19 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6599,6 +6804,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6722,7 +6941,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6752,6 +6971,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob/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/glob/node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -6795,6 +7021,20 @@ "node": ">=10.0" } }, + "node_modules/global-agent/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", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -6917,9 +7157,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6941,6 +7181,26 @@ "node": ">=10" } }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -6961,6 +7221,20 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -6975,6 +7249,20 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -6991,6 +7279,24 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7065,9 +7371,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -7185,11 +7491,14 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/ismobilejs": { "version": "1.1.1", @@ -7317,19 +7626,6 @@ "node": "20 || >=22" } }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -7405,60 +7701,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7511,19 +7753,6 @@ "node": ">=20" } }, - "node_modules/lint-staged/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/listr2": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", @@ -7542,19 +7771,6 @@ "node": ">=20.0.0" } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/listr2/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -7585,10 +7801,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "dev": true, "license": "MIT" }, @@ -7642,58 +7858,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -7738,19 +7902,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/log-update/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -7764,29 +7915,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, "node_modules/log-update/node_modules/is-fullwidth-code-point": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", @@ -7803,39 +7931,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/slice-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", @@ -7853,58 +7948,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7928,16 +7971,13 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/lucide-react": { @@ -7970,6 +8010,29 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -8017,12 +8080,6 @@ "url": "https://github.com/sponsors/Vanilagy" } }, - "node_modules/mediabunny/node_modules/@types/dom-webcodecs": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", - "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8045,6 +8102,18 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -8140,29 +8209,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/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/minimatch/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/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -8174,21 +8220,52 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "minipass": "^3.0.0" }, @@ -8209,6 +8286,13 @@ "node": ">=8" } }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -8235,6 +8319,13 @@ "node": ">=8" } }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", @@ -8261,6 +8352,40 @@ "node": ">=8" } }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/motion": { "version": "12.38.0", "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", @@ -8316,6 +8441,7 @@ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -8356,6 +8482,111 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", + "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/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/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/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/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/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/node-releases": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", @@ -8363,6 +8594,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8449,16 +8696,16 @@ } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8488,6 +8735,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -8514,6 +8817,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -8626,12 +8942,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -8693,11 +9009,30 @@ "url": "https://opencollective.com/pixijs" } }, + "node_modules/pixi.js/node_modules/@types/earcut": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==", + "license": "MIT" + }, + "node_modules/pixi.js/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "node_modules/pixi.js/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/playwright": { "version": "1.59.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "playwright-core": "1.59.1" }, @@ -8716,6 +9051,7 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -8738,6 +9074,16 @@ "node": ">=10.4.0" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", @@ -8954,14 +9300,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/proc-log": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", @@ -9007,6 +9345,12 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -9019,17 +9363,10 @@ "signal-exit": "^3.0.2" } }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", "dependencies": { @@ -9172,10 +9509,12 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -9188,9 +9527,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", - "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -9336,6 +9675,18 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -9395,12 +9746,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -9435,25 +9787,34 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/retry": { "version": "0.12.0", @@ -9482,6 +9843,21 @@ "dev": true, "license": "MIT" }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -9608,11 +9984,14 @@ } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/saxes": { "version": "6.0.0", @@ -9637,16 +10016,13 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/semver-compare": { @@ -9674,20 +10050,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9732,14 +10094,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "peer": true, "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -9795,17 +10157,11 @@ "license": "ISC" }, "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -9820,11 +10176,25 @@ "node": ">=10" } }, + "node_modules/simple-update-notifier/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/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, + "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -9834,6 +10204,22 @@ "node": ">=18" } }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -9860,6 +10246,21 @@ "npm": ">= 3.0.0" } }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -9908,6 +10309,19 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -9983,7 +10397,7 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -9996,6 +10410,35 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -10010,6 +10453,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -10164,6 +10620,33 @@ "jiti": "bin/jiti.js" } }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -10228,35 +10711,6 @@ "node": ">= 10.0.0" } }, - "node_modules/temp/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/terser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", @@ -10366,35 +10820,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", @@ -10462,10 +10887,24 @@ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", @@ -10501,6 +10940,20 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -10532,6 +10985,32 @@ "dev": true, "license": "MIT" }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -10673,6 +11152,22 @@ "uuid": "dist-node/bin/uuid" } }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/vite": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", @@ -10770,51 +11265,35 @@ "dev": true, "license": "MIT" }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/vitest": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", - "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -10842,12 +11321,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -10891,46 +11370,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -10999,19 +11438,19 @@ } }, "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/why-is-node-running": { @@ -11032,18 +11471,18 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -11068,6 +11507,57 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11080,6 +11570,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -11134,9 +11625,9 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, From 9613e714e125b80b2b2c9bf857996b474f09ae42 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 21 Apr 2026 15:06:57 +0200 Subject: [PATCH 123/152] chore: align @types/node with engine and fix package-lock.json cross-platform resolution --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4996323..4f854bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.6.0", + "@types/node": "^22.19.17", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^5.2.0", @@ -3983,13 +3983,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/plist": { @@ -10979,9 +10979,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 446976c..dbd5862 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.6.0", + "@types/node": "^22.19.17", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^5.2.0", From d823f3f01145ce186a03c6a99f46c9e9cc797a65 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Wed, 22 Apr 2026 12:27:15 +0200 Subject: [PATCH 124/152] Add Star History section to README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 074eaa7..37c1dd7 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,16 @@ See the documentation here: Contributions are welcome! If you’d like to help out or see what’s currently being worked on, take a look at the open issues and the [project roadmap](https://github.com/users/siddharthvaddem/projects/3) to understand the current direction of the project and find ways to contribute. +## Star History + + + + + + Star History Chart + + + ## License This project is licensed under the [MIT License](./LICENSE). By using this software, you agree that the authors are not liable for any issues, damages, or claims arising from its use. From d59ef6a8dd21a79c7e49909d7a5491a9714bb722 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Wed, 22 Apr 2026 16:06:25 +0200 Subject: [PATCH 125/152] Update README with additional badges for Trendshift (top repository of the day) and Discord badge update --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 37c1dd7..9ed0d1a 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,15 @@ OpenScreen Logo

+ siddharthvaddem%2Fopenscreen | Trendshift +
+
Ask DeepWiki   - Join Discord + Join Discord

From 9a361a9f2e6f7d529581e4520f71b5c83d91bdf0 Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Thu, 23 Apr 2026 15:10:59 +0530 Subject: [PATCH 126/152] fix(video-playback): resolve initialization timing issues and ensure smooth zoom & layout rendering --- src/components/video-editor/VideoPlayback.tsx | 79 ++++++++++++++----- src/lib/exporter/frameRenderer.ts | 8 +- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index d659afe..b4d98a6 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -164,6 +164,10 @@ const VideoPlayback = forwardRef( const [pixiReady, setPixiReady] = useState(false); const [videoReady, setVideoReady] = useState(false); const overlayRef = useRef(null); + const [containerSize, setContainerSize] = useState({ + width: 800, + height: 600, + }); const focusIndicatorRef = useRef(null); const [webcamLayout, setWebcamLayout] = useState(null); const [webcamDimensions, setWebcamDimensions] = useState(null); @@ -195,7 +199,10 @@ const VideoPlayback = forwardRef( const isPlayingRef = useRef(isPlaying); const isSeekingRef = useRef(false); const allowPlaybackRef = useRef(false); - const lockedVideoDimensionsRef = useRef<{ width: number; height: number } | null>(null); + const lockedVideoDimensionsRef = useRef<{ + width: number; + height: number; + } | null>(null); const layoutVideoContentRef = useRef<(() => void) | null>(null); const trimRegionsRef = useRef([]); const speedRegionsRef = useRef([]); @@ -648,7 +655,11 @@ const VideoPlayback = forwardRef( app.ticker.maxFPS = 60; if (!mounted) { - app.destroy(true, { children: true, texture: true, textureSource: true }); + app.destroy(true, { + children: true, + texture: true, + textureSource: true, + }); return; } @@ -672,7 +683,11 @@ const VideoPlayback = forwardRef( mounted = false; setPixiReady(false); if (app && app.renderer) { - app.destroy(true, { children: true, texture: true, textureSource: true }); + app.destroy(true, { + children: true, + texture: true, + textureSource: true, + }); } appRef.current = null; cameraContainerRef.current = null; @@ -853,12 +868,19 @@ const VideoPlayback = forwardRef( const ss = stageSizeRef.current; const viewportRatio = bm.width > 0 && bm.height > 0 - ? { widthRatio: ss.width / bm.width, heightRatio: ss.height / bm.height } + ? { + widthRatio: ss.width / bm.width, + heightRatio: ss.height / bm.height, + } : undefined; const { region, strength, blendedScale, transition } = findDominantRegion( zoomRegionsRef.current, currentTimeRef.current, - { connectZooms: true, cursorTelemetry: cursorTelemetryRef.current, viewportRatio }, + { + connectZooms: true, + cursorTelemetry: cursorTelemetryRef.current, + viewportRatio, + }, ); const defaultFocus = DEFAULT_FOCUS; @@ -1113,6 +1135,21 @@ const VideoPlayback = forwardRef( webcamVideo.currentTime = 0; }, [webcamVideoPath]); + useEffect(() => { + if (!overlayRef.current) return; + + const observer = new ResizeObserver(() => { + setContainerSize({ + width: overlayRef.current!.clientWidth, + height: overlayRef.current!.clientHeight, + }); + }); + + observer.observe(overlayRef.current); + + return () => observer.disconnect(); + }, []); + useEffect(() => { let mounted = true; (async () => { @@ -1307,20 +1344,24 @@ const VideoPlayback = forwardRef( } }; - return sorted.map((annotation) => ( - onAnnotationPositionChange?.(id, position)} - onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)} - onClick={handleAnnotationClick} - zIndex={annotation.zIndex} - isSelectedBoost={annotation.id === selectedAnnotationId} - /> - )); + return sorted.map((annotation) => { + const containerWidth = containerSize.width; + const containerHeight = containerSize.height; + return ( + onAnnotationPositionChange?.(id, position)} + onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)} + onClick={handleAnnotationClick} + zIndex={annotation.zIndex} + isSelectedBoost={annotation.id === selectedAnnotationId} + /> + ); + }); })()}
)} diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index 80d2f6d..be0e8e3 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -406,8 +406,8 @@ export class FrameRenderer { this.compositeCtx ) { // Calculate scale factor based on export vs preview dimensions - const previewWidth = this.config.previewWidth || 1920; - const previewHeight = this.config.previewHeight || 1080; + const previewWidth = this.config.previewWidth ?? this.config.width; + const previewHeight = this.config.previewHeight ?? this.config.height; const scaleX = this.config.width / previewWidth; const scaleY = this.config.height / previewHeight; const scaleFactor = (scaleX + scaleY) / 2; @@ -491,8 +491,8 @@ export class FrameRenderer { this.videoContainer.y = screenRect.y; // scale border radius by export/preview canvas ratio - const previewWidth = this.config.previewWidth || 1920; - const previewHeight = this.config.previewHeight || 1080; + const previewWidth = this.config.previewWidth ?? this.config.width; + const previewHeight = this.config.previewHeight ?? this.config.height; const canvasScaleFactor = Math.min(width / previewWidth, height / previewHeight); const scaledBorderRadius = compositeLayout.screenCover ? 0 : borderRadius * canvasScaleFactor; From f4e10b28cc23ede1d3529b568f24f7d2eae87da8 Mon Sep 17 00:00:00 2001 From: Raj Tiwari Date: Thu, 23 Apr 2026 22:50:21 +0530 Subject: [PATCH 127/152] style: fix linting errors for biome check --- src/components/ui/accordion.tsx | 2 +- src/components/ui/card.tsx | 2 +- src/components/ui/dialog.tsx | 12 ++++++------ src/components/ui/dropdown-menu.tsx | 14 +++++++------- src/components/ui/popover.tsx | 2 +- src/components/ui/select.tsx | 12 ++++++------ src/components/ui/tabs.tsx | 2 +- src/components/ui/tooltip.tsx | 2 +- src/components/video-editor/VideoPlayback.tsx | 7 ++++++- tsconfig.node.json | 7 ++++++- vite.config.ts | 12 +----------- 11 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index 85336fd..22456b2 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -52,4 +52,4 @@ const AccordionContent = React.forwardRef< )); AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 2935ed4..0480764 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -52,4 +52,4 @@ const CardFooter = React.forwardRef( const isMotionBlurActive = (motionBlurAmountRef.current || 0) > 0 && isPlayingRef.current; - if (isMotionBlurActive && blurFilterRef.current && motionBlurFilterRef.current && videoContainerRef.current) { + if ( + isMotionBlurActive && + blurFilterRef.current && + motionBlurFilterRef.current && + videoContainerRef.current + ) { videoContainerRef.current.filters = [blurFilterRef.current, motionBlurFilterRef.current]; } else if (videoContainerRef.current) { videoContainerRef.current.filters = null; diff --git a/tsconfig.node.json b/tsconfig.node.json index 1caabef..3be14b6 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -7,5 +7,10 @@ "allowSyntheticDefaultImports": true, "strict": true }, - "include": ["vite.config.ts"] + "include": [ + "vite.config.ts", + "electron-builder.json5", + "playwright.config.ts", + "vitest.config.ts" + ] } diff --git a/vite.config.ts b/vite.config.ts index 9a44766..5d014d4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,25 +9,15 @@ export default defineConfig({ react(), electron({ main: { - // Shortcut of `build.lib.entry`. entry: "electron/main.ts", vite: { build: {}, }, }, preload: { - // Shortcut of `build.rollupOptions.input`. - // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. input: path.join(__dirname, "electron/preload.ts"), }, - // Ployfill the Electron and Node.js API for Renderer process. - // If you want use Node.js in Renderer process, the `nodeIntegration` needs to be enabled in the Main process. - // See https://github.com/electron-vite/vite-plugin-electron-renderer - renderer: - process.env.NODE_ENV === "test" - ? // https://github.com/electron-vite/vite-plugin-electron-renderer/issues/78#issuecomment-2053600808 - undefined - : {}, + renderer: process.env.NODE_ENV === "test" ? undefined : {}, }), ], resolve: { From a26eb3cbab31437a35e238996bfa43fa236ee7ae Mon Sep 17 00:00:00 2001 From: Raj Tiwari Date: Thu, 23 Apr 2026 22:55:40 +0530 Subject: [PATCH 128/152] perf: cache motion blur state in ticker --- src/components/video-editor/VideoPlayback.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 48d3631..7b7ce55 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -856,7 +856,7 @@ const VideoPlayback = forwardRef( videoContainer.mask = null; maskGraphicsRef.current = null; if (blurFilterRef.current) { - videoContainer.filters = []; + videoContainer.filters = null; blurFilterRef.current.destroy(); blurFilterRef.current = null; } @@ -913,6 +913,7 @@ const VideoPlayback = forwardRef( state.appliedScale = appliedTransform.scale; }; + let lastMotionBlurActive: boolean | null = null; const ticker = () => { const bm = baseMaskRef.current; const ss = stageSizeRef.current; @@ -1083,15 +1084,16 @@ const VideoPlayback = forwardRef( const isMotionBlurActive = (motionBlurAmountRef.current || 0) > 0 && isPlayingRef.current; - if ( - isMotionBlurActive && - blurFilterRef.current && - motionBlurFilterRef.current && - videoContainerRef.current - ) { - videoContainerRef.current.filters = [blurFilterRef.current, motionBlurFilterRef.current]; - } else if (videoContainerRef.current) { - videoContainerRef.current.filters = null; + if (isMotionBlurActive !== lastMotionBlurActive && videoContainerRef.current) { + if (isMotionBlurActive && blurFilterRef.current && motionBlurFilterRef.current) { + videoContainerRef.current.filters = [ + blurFilterRef.current, + motionBlurFilterRef.current, + ]; + } else { + videoContainerRef.current.filters = null; + } + lastMotionBlurActive = isMotionBlurActive; } }; From 8e1c7e035af95fc0416b0c6335a38f3ef993325f Mon Sep 17 00:00:00 2001 From: Raj Tiwari Date: Thu, 23 Apr 2026 23:02:04 +0530 Subject: [PATCH 129/152] fix: correct motion blur state caching logic --- src/components/video-editor/VideoPlayback.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 7b7ce55..f15a801 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1085,15 +1085,18 @@ const VideoPlayback = forwardRef( const isMotionBlurActive = (motionBlurAmountRef.current || 0) > 0 && isPlayingRef.current; if (isMotionBlurActive !== lastMotionBlurActive && videoContainerRef.current) { - if (isMotionBlurActive && blurFilterRef.current && motionBlurFilterRef.current) { - videoContainerRef.current.filters = [ - blurFilterRef.current, - motionBlurFilterRef.current, - ]; + if (isMotionBlurActive) { + if (blurFilterRef.current && motionBlurFilterRef.current) { + videoContainerRef.current.filters = [ + blurFilterRef.current, + motionBlurFilterRef.current, + ]; + lastMotionBlurActive = true; + } } else { videoContainerRef.current.filters = null; + lastMotionBlurActive = false; } - lastMotionBlurActive = isMotionBlurActive; } }; From d1087af63c1600ff71b799408ee3f9bb89d558eb Mon Sep 17 00:00:00 2001 From: Marc Diaz Date: Thu, 23 Apr 2026 15:46:35 -0400 Subject: [PATCH 130/152] fix: lint --- src/lib/exporter/gifExporter.browser.test.ts | 30 +++++++++++++++++++ .../exporter/videoExporter.browser.test.ts | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/lib/exporter/gifExporter.browser.test.ts b/src/lib/exporter/gifExporter.browser.test.ts index db9b144..1a7b638 100644 --- a/src/lib/exporter/gifExporter.browser.test.ts +++ b/src/lib/exporter/gifExporter.browser.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; +import inflatedDurationVideoUrl from "../../../tests/fixtures/sample-inflated-duration.webm?url"; import { GifExporter } from "./gifExporter"; import type { ExportProgress } from "./types"; @@ -40,4 +41,33 @@ describe("GifExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); + + it("exports successfully when container duration is inflated beyond actual content", async () => { + const exporter = new GifExporter({ + videoUrl: inflatedDurationVideoUrl, + width: 320, + height: 180, + frameRate: 15, + loop: true, + sizePreset: "medium", + wallpaper: "#1a1a2e", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + onProgress: () => { + /**noop**/ + }, + }); + + const result = await exporter.export(); + + expect(result.success, result.error).toBe(true); + expect(result.blob).toBeInstanceOf(Blob); + + const buf = await result.blob!.arrayBuffer(); + const header = new TextDecoder().decode(new Uint8Array(buf, 0, 6)); + expect(header).toMatch(/^GIF8[79]a/); + }); }); diff --git a/src/lib/exporter/videoExporter.browser.test.ts b/src/lib/exporter/videoExporter.browser.test.ts index ec2b0f6..4607111 100644 --- a/src/lib/exporter/videoExporter.browser.test.ts +++ b/src/lib/exporter/videoExporter.browser.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; +import inflatedDurationVideoUrl from "../../../tests/fixtures/sample-inflated-duration.webm?url"; import type { ExportProgress } from "./types"; import { VideoExporter } from "./videoExporter"; @@ -40,4 +41,33 @@ describe("VideoExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); + + it("exports successfully when container duration is inflated beyond actual content", async () => { + const exporter = new VideoExporter({ + videoUrl: inflatedDurationVideoUrl, + width: 320, + height: 180, + frameRate: 15, + bitrate: 1_000_000, + wallpaper: "#1a1a2e", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + onProgress: () => { + /**noop**/ + }, + }); + + const result = await exporter.export(); + + expect(result.success, result.error).toBe(true); + expect(result.blob).toBeInstanceOf(Blob); + + const buf = await result.blob!.arrayBuffer(); + const bytes = new Uint8Array(buf); + const ftyp = new TextDecoder().decode(bytes.slice(4, 8)); + expect(ftyp).toBe("ftyp"); + }); }); From cffca5f2ffa62924bdc66cfaf53ff671c3291760 Mon Sep 17 00:00:00 2001 From: Marc Diaz Date: Thu, 23 Apr 2026 17:37:08 -0400 Subject: [PATCH 131/152] fix: just use one test --- package-lock.json | 12 ++++--- src/lib/exporter/gifExporter.browser.test.ts | 30 ------------------ src/lib/exporter/streamingDecoder.test.ts | 14 ++++++++ src/lib/exporter/streamingDecoder.ts | 19 ++++------- .../exporter/videoExporter.browser.test.ts | 30 ------------------ tests/fixtures/sample-inflated-duration.webm | Bin 1252 -> 0 bytes vitest.config.ts | 3 +- 7 files changed, 29 insertions(+), 79 deletions(-) delete mode 100644 tests/fixtures/sample-inflated-duration.webm diff --git a/package-lock.json b/package-lock.json index ba40beb..ed0c9af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7222,13 +7222,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.15", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.15.tgz", - "integrity": "sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==", + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", "dev": true, - "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bcrypt-pbkdf": { diff --git a/src/lib/exporter/gifExporter.browser.test.ts b/src/lib/exporter/gifExporter.browser.test.ts index 1a7b638..db9b144 100644 --- a/src/lib/exporter/gifExporter.browser.test.ts +++ b/src/lib/exporter/gifExporter.browser.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; -import inflatedDurationVideoUrl from "../../../tests/fixtures/sample-inflated-duration.webm?url"; import { GifExporter } from "./gifExporter"; import type { ExportProgress } from "./types"; @@ -41,33 +40,4 @@ describe("GifExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); - - it("exports successfully when container duration is inflated beyond actual content", async () => { - const exporter = new GifExporter({ - videoUrl: inflatedDurationVideoUrl, - width: 320, - height: 180, - frameRate: 15, - loop: true, - sizePreset: "medium", - wallpaper: "#1a1a2e", - zoomRegions: [], - showShadow: false, - shadowIntensity: 0, - showBlur: false, - cropRegion: { x: 0, y: 0, width: 1, height: 1 }, - onProgress: () => { - /**noop**/ - }, - }); - - const result = await exporter.export(); - - expect(result.success, result.error).toBe(true); - expect(result.blob).toBeInstanceOf(Blob); - - const buf = await result.blob!.arrayBuffer(); - const header = new TextDecoder().decode(new Uint8Array(buf, 0, 6)); - expect(header).toMatch(/^GIF8[79]a/); - }); }); diff --git a/src/lib/exporter/streamingDecoder.test.ts b/src/lib/exporter/streamingDecoder.test.ts index 55b9123..45be92b 100644 --- a/src/lib/exporter/streamingDecoder.test.ts +++ b/src/lib/exporter/streamingDecoder.test.ts @@ -83,4 +83,18 @@ describe("shouldFailDecodeEndedEarly", () => { }), ).toBe(true); }); + + it("does not fail when decoder reached stream end but container tail is large (inflated metadata)", () => { + // Real case: ~20min video where container reports 1234s but actual stream + // ends at 1226s. Decoder correctly stops at 1226s (= streamDurationSec). + // The 8s tail is container metadata inflation, not a real decode failure. + expect( + shouldFailDecodeEndedEarly({ + cancelled: false, + lastDecodedFrameSec: 1226, + requiredEndSec: 1234, + streamDurationSec: 1226, + }), + ).toBe(false); + }); }); diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index c9a9597..b64c5ab 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -129,11 +129,8 @@ export function shouldFailDecodeEndedEarly({ const decodedNearStreamEnd = Math.abs(lastDecodedFrameSec - streamDurationSec) <= STREAM_DURATION_MATCH_TOLERANCE_SEC; - if ( - decodedNearStreamEnd && - metadataTailSec > 0 && - metadataTailSec <= METADATA_TAIL_TOLERANCE_SEC - ) { + const maxTailSec = Math.max(METADATA_TAIL_TOLERANCE_SEC, requiredEndSec * 0.01); + if (decodedNearStreamEnd && metadataTailSec > 0 && metadataTailSec <= maxTailSec) { return false; } @@ -246,13 +243,14 @@ export class StreamingVideoDecoder { const hintedDurationSec = Math.max(containerDurationSec, streamDurationSec, 0); const scanEndSec = hintedDurationSec > 0 ? hintedDurationSec + 0.5 : SCAN_UNBOUNDED_FALLBACK_SEC; - let maxPacketTimestampUs = 0; + let maxPacketEndUs = 0; const scanReader = this.demuxer.read("video", 0, scanEndSec).getReader(); try { while (true) { const { done, value } = await scanReader.read(); if (done || !value) break; - if (value.timestamp > maxPacketTimestampUs) maxPacketTimestampUs = value.timestamp; + const endUs = value.timestamp + (value.duration ?? 0); + if (endUs > maxPacketEndUs) maxPacketEndUs = endUs; } } finally { try { @@ -261,12 +259,7 @@ export class StreamingVideoDecoder { /* already closed */ } } - // Use last frame's timestamp + one frame duration. The `duration` field on the last - // packet in a WebM container is frequently inflated by the demuxer to match the - // container's declared end, causing validateDuration to see no divergence and - // propagate a duration that the decoder can never actually reach. - const typicalFrameDurationUs = Math.ceil(1_000_000 / frameRate); - const scannedDuration = (maxPacketTimestampUs + typicalFrameDurationUs) / 1_000_000; + const scannedDuration = maxPacketEndUs / 1_000_000; const validatedDuration = validateDuration(mediaInfo.duration, scannedDuration); this.metadata = { diff --git a/src/lib/exporter/videoExporter.browser.test.ts b/src/lib/exporter/videoExporter.browser.test.ts index 4607111..ec2b0f6 100644 --- a/src/lib/exporter/videoExporter.browser.test.ts +++ b/src/lib/exporter/videoExporter.browser.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; -import inflatedDurationVideoUrl from "../../../tests/fixtures/sample-inflated-duration.webm?url"; import type { ExportProgress } from "./types"; import { VideoExporter } from "./videoExporter"; @@ -41,33 +40,4 @@ describe("VideoExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); - - it("exports successfully when container duration is inflated beyond actual content", async () => { - const exporter = new VideoExporter({ - videoUrl: inflatedDurationVideoUrl, - width: 320, - height: 180, - frameRate: 15, - bitrate: 1_000_000, - wallpaper: "#1a1a2e", - zoomRegions: [], - showShadow: false, - shadowIntensity: 0, - showBlur: false, - cropRegion: { x: 0, y: 0, width: 1, height: 1 }, - onProgress: () => { - /**noop**/ - }, - }); - - const result = await exporter.export(); - - expect(result.success, result.error).toBe(true); - expect(result.blob).toBeInstanceOf(Blob); - - const buf = await result.blob!.arrayBuffer(); - const bytes = new Uint8Array(buf); - const ftyp = new TextDecoder().decode(bytes.slice(4, 8)); - expect(ftyp).toBe("ftyp"); - }); }); diff --git a/tests/fixtures/sample-inflated-duration.webm b/tests/fixtures/sample-inflated-duration.webm deleted file mode 100644 index 6322840dba15e3d790d34e6e656c054f7e772bbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1252 zcmcK4O=uHA6ae72Nz_6Q{t+!wXt2dY#gJ55tf$E)QKP1MfTH5>`Vn_oy8H?!-(FI>vv7l ziE6aXoxdPQZ=A(Z`D}}OPH;_!xTb1Y?<&fNA>D(o)jDu;wcP*Ml&%WR3y;DZdEyjD zkM;)p+ggl!jb@YSxCMo_YY8%!SnWY+{$*b6jVw^kKcCpO-0&)w37G3sw|7`O zZT4?zqLc`m+;{T<84b>!Kr|G_4qe65eU7yBf0 z!2U^fYX-S)H*|(gMFCg6YNFifPF=EOBwlk!d${0 tW-el%XYNJ5p@z8u`w`}V{krO;yynV1GgiFugu?Tre4l*}ak)jR{R8OYZ`=R? diff --git a/vitest.config.ts b/vitest.config.ts index ea60216..8ad92d3 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,8 +4,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - environment: "jsdom", + environment: "node", include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + exclude: ["src/**/*.browser.test.{ts,tsx}", "node_modules"], }, resolve: { alias: { From 466fad399adb58146bbe14cb452b08e5da896075 Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Fri, 24 Apr 2026 15:28:53 +0530 Subject: [PATCH 132/152] fix: seed overlaySize on mount and guard ResizeObserver entries --- src/components/video-editor/VideoPlayback.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 71e5a4f..e48dc3a 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -542,21 +542,22 @@ const VideoPlayback = forwardRef( useEffect(() => { if (!pixiReady || !videoReady) return; - const el = overlayRef.current; if (!el) return; - const observer = new ResizeObserver((entries) => { - const { width, height } = entries[0].contentRect; + // Seed immediately so overlays never start at 800×600 + setOverlaySize({ width: el.clientWidth, height: el.clientHeight }); - setOverlaySize({ - width, - height, + const observer = new ResizeObserver((entries) => { + if (!entries[0]) return; + const { width, height } = entries[0].contentRect; + setOverlaySize((prev) => { + if (prev.width === width && prev.height === height) return prev; + return { width, height }; }); }); observer.observe(el); - return () => observer.disconnect(); }, [pixiReady, videoReady]); From d145f800415545f444193727455c063595f35c76 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 17:59:21 -0500 Subject: [PATCH 133/152] fix: wallpaper backgrounds black in exported video (#376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three independent defects plus one SSOT violation caused reported symptom of image wallpapers rendering solid black in exported MP4/GIF while appearing correctly in the editor preview. Bug A — Dev-mode IPC handler returned /public/assets/, but wallpapers live at public/wallpapers/. No assets/ subdirectory exists in source. Bug B — FrameRenderer.setupBackground bypassed getAssetPath and did window.location.origin + wallpaper, producing file:///wallpapers/*.jpg 404s in packaged Electron. Bug C — setupBackground silently caught any background-load error and filled black. Masked Bug B from the export pipeline; why the bug shipped. Smell D — Asset layout asymmetric: public/wallpapers/ (dev) vs resources/assets/wallpapers/ (packaged). assets/ subdirectory had no other consumers. Fixes: - Unify asset layout. electron-builder extraResources now copies to resources/wallpapers/ (no assets/). Main handler returns / packaged and /public/ unpackaged. Same convention in both modes: /wallpapers/x.jpg maps to /wallpapers/x.jpg. Nix package.nix mirror updated. - New src/lib/wallpaper.ts module owns the wallpaper contract: DEFAULT_WALLPAPER, classifyWallpaper (color/gradient/image), and resolveImageWallpaperUrl (pure URL resolver, wraps getAssetPath). BackgroundLoadError typed error for short-circuit detection. - FrameRenderer.setupBackground uses the new helpers. Silent black fallback removed; rethrows as BackgroundLoadError. Export pipeline (VideoExporter + GifExporter) short-circuits encoder-retry loop on BackgroundLoadError. VideoEditor catch site dispatches to translated exportBackgroundLoadFailed toast. - VideoPlayback editor preview consolidated onto the same helpers. Three default-wallpaper path literals (useEditorHistory, projectPersistence, VideoPlayback) collapsed onto DEFAULT_WALLPAPER. - i18n: new errors.exportBackgroundLoadFailed key added to all seven locales (en, zh-CN, zh-TW, es, fr, tr, ko-KR). - Tests: 20 unit tests for wallpaper module (classifyWallpaper + resolveImageWallpaperUrl branches + BackgroundLoadError). videoExporter.browser.test.ts and gifExporter.browser.test.ts extended with image-wallpaper happy path and BackgroundLoadError failure path. Migration note: packaged users upgrading in place may retain an empty resources/assets/ directory from the prior layout. Unreferenced at runtime; cosmetic only. DMG/AppImage fresh installs get the new layout directly. --- electron-builder.json5 | 2 +- electron/ipc/handlers.ts | 15 +- nix/package.nix | 8 +- src/components/video-editor/VideoEditor.tsx | 14 +- src/components/video-editor/VideoPlayback.tsx | 51 +---- .../video-editor/projectPersistence.ts | 3 +- src/hooks/useEditorHistory.ts | 3 +- src/i18n/locales/en/editor.json | 1 + src/i18n/locales/es/editor.json | 1 + src/i18n/locales/fr/editor.json | 1 + src/i18n/locales/ko-KR/editor.json | 1 + src/i18n/locales/tr/editor.json | 1 + src/i18n/locales/zh-CN/editor.json | 1 + src/i18n/locales/zh-TW/editor.json | 1 + src/lib/exporter/frameRenderer.ts | 179 ++++++++---------- src/lib/exporter/gifExporter.browser.test.ts | 41 ++++ src/lib/exporter/gifExporter.ts | 4 + .../exporter/videoExporter.browser.test.ts | 39 ++++ src/lib/exporter/videoExporter.ts | 6 + src/lib/wallpaper.test.ts | 167 ++++++++++++++++ src/lib/wallpaper.ts | 43 +++++ 21 files changed, 418 insertions(+), 164 deletions(-) create mode 100644 src/lib/wallpaper.test.ts create mode 100644 src/lib/wallpaper.ts diff --git a/electron-builder.json5 b/electron-builder.json5 index 18498df..441eda4 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -23,7 +23,7 @@ "extraResources": [ { "from": "public/wallpapers", - "to": "assets/wallpapers" + "to": "wallpapers" } ], diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 261d93f..6aec971 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -801,15 +801,16 @@ export function registerIpcHandlers( } }); - // Return base path for assets so renderer can resolve file:// paths in production + // Return base path for assets so renderer can resolve file:// paths in production. + // Packaged: electron-builder extraResources copies public/wallpapers -> resources/wallpapers. + // Unpackaged: wallpapers live at /public/wallpapers. + // Single convention: "/wallpapers/x.jpg" resolves in both modes. ipcMain.handle("get-asset-base-path", () => { try { - if (app.isPackaged) { - const assetPath = path.join(process.resourcesPath, "assets"); - return pathToFileURL(`${assetPath}${path.sep}`).toString(); - } - const assetPath = path.join(app.getAppPath(), "public", "assets"); - return pathToFileURL(`${assetPath}${path.sep}`).toString(); + const baseDir = app.isPackaged + ? process.resourcesPath + : path.join(app.getAppPath(), "public"); + return pathToFileURL(`${baseDir}${path.sep}`).toString(); } catch (err) { console.error("Failed to resolve asset base path:", err); return null; diff --git a/nix/package.nix b/nix/package.nix index 195043f..13a8658 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -68,10 +68,10 @@ buildNpmPackage { cp -r node_modules "$out/lib/openscreen/" # Asset resolution: when app.isPackaged is false, the main process resolves - # assets at /public/assets/. Mirror the electron-builder - # extraResources layout so wallpapers load correctly. - mkdir -p "$out/lib/openscreen/public/assets" - cp -r public/wallpapers "$out/lib/openscreen/public/assets/wallpapers" + # assets at /public/. Place wallpapers at that root to match the + # packaged layout (electron-builder extraResources -> resources/wallpapers). + mkdir -p "$out/lib/openscreen/public" + cp -r public/wallpapers "$out/lib/openscreen/public/wallpapers" # Wrap system electron with the app directory mkdir -p "$out/bin" diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 6d21d13..3694d9e 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -32,6 +32,7 @@ import { computeFrameStepTime } from "@/lib/frameStep"; import type { ProjectMedia } from "@/lib/recordingSession"; import { matchesShortcut } from "@/lib/shortcuts"; import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences"; +import { BackgroundLoadError } from "@/lib/wallpaper"; import { getAspectRatioValue, getNativeAspectRatioValue, @@ -1566,9 +1567,15 @@ export default function VideoEditor() { } } catch (error) { console.error("Export error:", error); - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - setExportError(errorMessage); - toast.error(`Export failed: ${errorMessage}`); + if (error instanceof BackgroundLoadError) { + const message = t("errors.exportBackgroundLoadFailed", { url: error.url }); + setExportError(message); + toast.error(message); + } else { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + setExportError(errorMessage); + toast.error(t("errors.exportFailedWithError", { error: errorMessage })); + } } finally { setIsExporting(false); exporterRef.current = null; @@ -1601,6 +1608,7 @@ export default function VideoEditor() { exportQuality, handleExportSaved, cursorTelemetry, + t, ], ); diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index b798641..db69310 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -18,7 +18,6 @@ import { useRef, useState, } from "react"; -import { getAssetPath } from "@/lib/assetPath"; import { getWebcamLayoutCssBoxShadow, type Size, @@ -26,6 +25,7 @@ import { type WebcamLayoutPreset, type WebcamSizePreset, } from "@/lib/compositeLayout"; +import { classifyWallpaper, DEFAULT_WALLPAPER, resolveImageWallpaperUrl } from "@/lib/wallpaper"; import { getCssClipPath } from "@/lib/webcamMaskShapes"; import { type AspectRatio, @@ -1179,49 +1179,14 @@ const VideoPlayback = forwardRef( useEffect(() => { let mounted = true; (async () => { - try { - if (!wallpaper) { - const def = await getAssetPath("wallpapers/wallpaper1.jpg"); - if (mounted) setResolvedWallpaper(def); - return; - } - - if ( - wallpaper.startsWith("#") || - wallpaper.startsWith("linear-gradient") || - wallpaper.startsWith("radial-gradient") - ) { - if (mounted) setResolvedWallpaper(wallpaper); - return; - } - - // If it's a data URL (custom uploaded image), use as-is - if (wallpaper.startsWith("data:")) { - if (mounted) setResolvedWallpaper(wallpaper); - return; - } - - // If it's an absolute web/http or file path, use as-is - if ( - wallpaper.startsWith("http") || - wallpaper.startsWith("file://") || - wallpaper.startsWith("/") - ) { - // If it's an absolute server path (starts with '/'), resolve via getAssetPath as well - if (wallpaper.startsWith("/")) { - const rel = wallpaper.replace(/^\//, ""); - const p = await getAssetPath(rel); - if (mounted) setResolvedWallpaper(p); - return; - } - if (mounted) setResolvedWallpaper(wallpaper); - return; - } - const p = await getAssetPath(wallpaper.replace(/^\//, "")); - if (mounted) setResolvedWallpaper(p); - } catch (_err) { - if (mounted) setResolvedWallpaper(wallpaper || "/wallpapers/wallpaper1.jpg"); + const source = wallpaper || DEFAULT_WALLPAPER; + const classified = classifyWallpaper(source); + if (classified.kind !== "image") { + if (mounted) setResolvedWallpaper(classified.value); + return; } + const resolved = await resolveImageWallpaperUrl(classified.path); + if (mounted) setResolvedWallpaper(resolved); })(); return () => { mounted = false; diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index c085e0d..f9640e4 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -2,6 +2,7 @@ import { normalizeBlurColor, normalizeBlurType } from "@/lib/blurEffects"; import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter"; import type { ProjectMedia } from "@/lib/recordingSession"; import { normalizeProjectMedia } from "@/lib/recordingSession"; +import { DEFAULT_WALLPAPER } from "@/lib/wallpaper"; import { ASPECT_RATIOS, type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { type AnnotationRegion, @@ -425,7 +426,7 @@ export function normalizeProjectEditor(editor: Partial): Pro const cropHeight = clamp(rawCropHeight, 0.01, 1 - cropY); return { - wallpaper: typeof editor.wallpaper === "string" ? editor.wallpaper : WALLPAPER_PATHS[0], + wallpaper: typeof editor.wallpaper === "string" ? editor.wallpaper : DEFAULT_WALLPAPER, shadowIntensity: typeof editor.shadowIntensity === "number" ? editor.shadowIntensity : 0, showBlur: typeof editor.showBlur === "boolean" ? editor.showBlur : false, motionBlurAmount: isFiniteNumber(editor.motionBlurAmount) diff --git a/src/hooks/useEditorHistory.ts b/src/hooks/useEditorHistory.ts index cc19222..bd410da 100644 --- a/src/hooks/useEditorHistory.ts +++ b/src/hooks/useEditorHistory.ts @@ -17,6 +17,7 @@ import { DEFAULT_WEBCAM_POSITION, DEFAULT_WEBCAM_SIZE_PRESET, } from "@/components/video-editor/types"; +import { DEFAULT_WALLPAPER } from "@/lib/wallpaper"; import type { AspectRatio } from "@/utils/aspectRatioUtils"; // Undoable state — selection IDs are intentionally excluded (undoing a @@ -46,7 +47,7 @@ export const INITIAL_EDITOR_STATE: EditorState = { speedRegions: [], annotationRegions: [], cropRegion: DEFAULT_CROP_REGION, - wallpaper: "/wallpapers/wallpaper1.jpg", + wallpaper: DEFAULT_WALLPAPER, shadowIntensity: 0, showBlur: false, motionBlurAmount: 0, diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index a171b16..254272d 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -14,6 +14,7 @@ "failedToSaveVideo": "Failed to save video", "exportFailed": "Export failed", "exportFailedWithError": "Export failed: {{error}}", + "exportBackgroundLoadFailed": "Export failed: could not load background image ({{url}})", "failedToSaveExport": "Failed to save export", "failedToSaveExportedVideo": "Failed to save exported video", "failedToRevealInFolder": "Error revealing in folder: {{error}}" diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index 7956b75..c71368a 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -8,6 +8,7 @@ "failedToSaveVideo": "Error al guardar el video", "exportFailed": "La exportación falló", "exportFailedWithError": "La exportación falló: {{error}}", + "exportBackgroundLoadFailed": "La exportación falló: no se pudo cargar la imagen de fondo ({{url}})", "failedToSaveExport": "Error al guardar la exportación", "failedToSaveExportedVideo": "Error al guardar el video exportado", "failedToRevealInFolder": "Error al mostrar en la carpeta: {{error}}" diff --git a/src/i18n/locales/fr/editor.json b/src/i18n/locales/fr/editor.json index 03596bd..ae48fdf 100644 --- a/src/i18n/locales/fr/editor.json +++ b/src/i18n/locales/fr/editor.json @@ -14,6 +14,7 @@ "failedToSaveVideo": "Échec de l'enregistrement de la vidéo", "exportFailed": "L'export a échoué", "exportFailedWithError": "L'export a échoué : {{error}}", + "exportBackgroundLoadFailed": "L'export a échoué : impossible de charger l'image d'arrière-plan ({{url}})", "failedToSaveExport": "Échec de l'enregistrement de l'export", "failedToSaveExportedVideo": "Échec de l'enregistrement de la vidéo exportée", "failedToRevealInFolder": "Erreur lors de l'affichage dans le dossier : {{error}}" diff --git a/src/i18n/locales/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json index 4db7d1f..eed0261 100644 --- a/src/i18n/locales/ko-KR/editor.json +++ b/src/i18n/locales/ko-KR/editor.json @@ -14,6 +14,7 @@ "failedToSaveVideo": "비디오 저장에 실패했습니다", "exportFailed": "내보내기에 실패했습니다", "exportFailedWithError": "내보내기 실패: {{error}}", + "exportBackgroundLoadFailed": "내보내기 실패: 배경 이미지를 불러올 수 없습니다 ({{url}})", "failedToSaveExport": "내보낸 파일 저장에 실패했습니다", "failedToSaveExportedVideo": "내보낸 비디오 저장에 실패했습니다", "failedToRevealInFolder": "폴더에서 파일 표시 오류: {{error}}" diff --git a/src/i18n/locales/tr/editor.json b/src/i18n/locales/tr/editor.json index dfa4cb1..c34d64b 100644 --- a/src/i18n/locales/tr/editor.json +++ b/src/i18n/locales/tr/editor.json @@ -8,6 +8,7 @@ "failedToSaveVideo": "Video kaydedilemedi", "exportFailed": "Dışa aktarım başarısız oldu", "exportFailedWithError": "Dışa aktarım başarısız: {{error}}", + "exportBackgroundLoadFailed": "Dışa aktarım başarısız: arka plan görüntüsü yüklenemedi ({{url}})", "failedToSaveExport": "Dışa aktarım kaydedilemedi", "failedToSaveExportedVideo": "Dışa aktarılan video kaydedilemedi", "failedToRevealInFolder": "Klasörde gösterme hatası: {{error}}" diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 1980354..f2eba2e 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -14,6 +14,7 @@ "failedToSaveVideo": "保存视频失败", "exportFailed": "导出失败", "exportFailedWithError": "导出失败:{{error}}", + "exportBackgroundLoadFailed": "导出失败:无法加载背景图片({{url}})", "failedToSaveExport": "保存导出文件失败", "failedToSaveExportedVideo": "保存导出的视频失败", "failedToRevealInFolder": "在文件夹中显示时出错:{{error}}" diff --git a/src/i18n/locales/zh-TW/editor.json b/src/i18n/locales/zh-TW/editor.json index 73a3f4e..ee502fb 100644 --- a/src/i18n/locales/zh-TW/editor.json +++ b/src/i18n/locales/zh-TW/editor.json @@ -14,6 +14,7 @@ "failedToSaveVideo": "儲存影片失敗", "exportFailed": "匯出失敗", "exportFailedWithError": "匯出失敗:{{error}}", + "exportBackgroundLoadFailed": "匯出失敗:無法載入背景圖片({{url}})", "failedToSaveExport": "儲存匯出檔案失敗", "failedToSaveExportedVideo": "儲存匯出的影片失敗", "failedToRevealInFolder": "在資料夾中顯示時出錯:{{error}}" diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index 9b1cf6d..e7362eb 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -45,6 +45,7 @@ import { type Size, type StyledRenderRect, } from "@/lib/compositeLayout"; +import { BackgroundLoadError, classifyWallpaper, resolveImageWallpaperUrl } from "@/lib/wallpaper"; import { drawCanvasClipPath } from "@/lib/webcamMaskShapes"; import { renderAnnotations } from "./annotationRenderer"; import { @@ -231,123 +232,93 @@ export class FrameRenderer { private async setupBackground(): Promise { const wallpaper = this.config.wallpaper; - // Create background canvas for separate rendering (not affected by zoom) const bgCanvas = document.createElement("canvas"); bgCanvas.width = this.config.width; bgCanvas.height = this.config.height; const bgCtx = bgCanvas.getContext("2d")!; - try { - // Render background based on type - if ( - wallpaper.startsWith("file://") || - wallpaper.startsWith("data:") || - wallpaper.startsWith("/") || - wallpaper.startsWith("http") - ) { - // Image background - const img = new Image(); - // Don't set crossOrigin for same-origin images to avoid CORS taint - // Only set it for cross-origin URLs - let imageUrl: string; - if (wallpaper.startsWith("http")) { - imageUrl = wallpaper; - if (!imageUrl.startsWith(window.location.origin)) { - img.crossOrigin = "anonymous"; - } - } else if (wallpaper.startsWith("file://") || wallpaper.startsWith("data:")) { - imageUrl = wallpaper; - } else { - imageUrl = window.location.origin + wallpaper; - } + const classified = classifyWallpaper(wallpaper); + if (classified.kind === "color") { + bgCtx.fillStyle = classified.value; + bgCtx.fillRect(0, 0, this.config.width, this.config.height); + } else if (classified.kind === "gradient") { + const parsedGradient = parseCssGradient(classified.value); + if (!parsedGradient) { + throw new BackgroundLoadError(classified.value); + } + const gradient = + parsedGradient.type === "linear" + ? (() => { + const points = getLinearGradientPoints( + resolveLinearGradientAngle(parsedGradient.descriptor), + this.config.width, + this.config.height, + ); + return bgCtx.createLinearGradient(points.x0, points.y0, points.x1, points.y1); + })() + : (() => { + const shape = getRadialGradientShape( + parsedGradient.descriptor, + this.config.width, + this.config.height, + ); + return bgCtx.createRadialGradient( + shape.cx, + shape.cy, + 0, + shape.cx, + shape.cy, + shape.radius, + ); + })(); + + parsedGradient.stops.forEach((stop) => { + gradient.addColorStop(stop.offset, stop.color); + }); + + bgCtx.fillStyle = gradient; + bgCtx.fillRect(0, 0, this.config.width, this.config.height); + } else { + const imageUrl = await resolveImageWallpaperUrl(classified.path); + const img = new Image(); + if (imageUrl.startsWith("http") && !imageUrl.startsWith(window.location.origin)) { + img.crossOrigin = "anonymous"; + } + + try { await new Promise((resolve, reject) => { img.onload = () => resolve(); - img.onerror = (err) => { - console.error("[FrameRenderer] Failed to load background image:", imageUrl, err); - reject(new Error(`Failed to load background image: ${imageUrl}`)); - }; + img.onerror = (err) => reject(err); img.src = imageUrl; }); - - // Draw the image using cover and center positioning - const imgAspect = img.width / img.height; - const canvasAspect = this.config.width / this.config.height; - - let drawWidth, drawHeight, drawX, drawY; - - if (imgAspect > canvasAspect) { - drawHeight = this.config.height; - drawWidth = drawHeight * imgAspect; - drawX = (this.config.width - drawWidth) / 2; - drawY = 0; - } else { - drawWidth = this.config.width; - drawHeight = drawWidth / imgAspect; - drawX = 0; - drawY = (this.config.height - drawHeight) / 2; - } - - bgCtx.drawImage(img, drawX, drawY, drawWidth, drawHeight); - } else if (wallpaper.startsWith("#")) { - bgCtx.fillStyle = wallpaper; - bgCtx.fillRect(0, 0, this.config.width, this.config.height); - } else if ( - wallpaper.startsWith("linear-gradient") || - wallpaper.startsWith("radial-gradient") - ) { - const parsedGradient = parseCssGradient(wallpaper); - if (parsedGradient) { - const gradient = - parsedGradient.type === "linear" - ? (() => { - const points = getLinearGradientPoints( - resolveLinearGradientAngle(parsedGradient.descriptor), - this.config.width, - this.config.height, - ); - - return bgCtx.createLinearGradient(points.x0, points.y0, points.x1, points.y1); - })() - : (() => { - const shape = getRadialGradientShape( - parsedGradient.descriptor, - this.config.width, - this.config.height, - ); - - return bgCtx.createRadialGradient( - shape.cx, - shape.cy, - 0, - shape.cx, - shape.cy, - shape.radius, - ); - })(); - - parsedGradient.stops.forEach((stop) => { - gradient.addColorStop(stop.offset, stop.color); - }); - - bgCtx.fillStyle = gradient; - bgCtx.fillRect(0, 0, this.config.width, this.config.height); - } else { - console.warn("[FrameRenderer] Could not parse gradient, using black fallback"); - bgCtx.fillStyle = "#000000"; - bgCtx.fillRect(0, 0, this.config.width, this.config.height); - } - } else { - bgCtx.fillStyle = wallpaper; - bgCtx.fillRect(0, 0, this.config.width, this.config.height); + } catch (err) { + throw new BackgroundLoadError(imageUrl, err); } - } catch (error) { - console.error("[FrameRenderer] Error setting up background, using fallback:", error); - bgCtx.fillStyle = "#000000"; - bgCtx.fillRect(0, 0, this.config.width, this.config.height); + + const imgAspect = img.width / img.height; + const canvasAspect = this.config.width / this.config.height; + + let drawWidth: number; + let drawHeight: number; + let drawX: number; + let drawY: number; + + if (imgAspect > canvasAspect) { + drawHeight = this.config.height; + drawWidth = drawHeight * imgAspect; + drawX = (this.config.width - drawWidth) / 2; + drawY = 0; + } else { + drawWidth = this.config.width; + drawHeight = drawWidth / imgAspect; + drawX = 0; + drawY = (this.config.height - drawHeight) / 2; + } + + bgCtx.drawImage(img, drawX, drawY, drawWidth, drawHeight); } - // Store the background canvas for compositing this.backgroundSprite = bgCanvas; } diff --git a/src/lib/exporter/gifExporter.browser.test.ts b/src/lib/exporter/gifExporter.browser.test.ts index db9b144..5a69468 100644 --- a/src/lib/exporter/gifExporter.browser.test.ts +++ b/src/lib/exporter/gifExporter.browser.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; +import { BackgroundLoadError } from "../wallpaper"; import { GifExporter } from "./gifExporter"; import type { ExportProgress } from "./types"; @@ -40,4 +41,44 @@ describe("GifExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); + + it("exports successfully with an image wallpaper (served by Vite dev server)", async () => { + const exporter = new GifExporter({ + videoUrl: sampleVideoUrl, + width: 320, + height: 180, + frameRate: 15, + loop: true, + sizePreset: "medium", + wallpaper: "/wallpapers/wallpaper1.jpg", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + }); + + const result = await exporter.export(); + expect(result.success, result.error).toBe(true); + expect(result.blob!.size).toBeGreaterThan(1024); + }); + + it("throws BackgroundLoadError when wallpaper fails to load (no silent black fallback)", async () => { + const exporter = new GifExporter({ + videoUrl: sampleVideoUrl, + width: 320, + height: 180, + frameRate: 15, + loop: true, + sizePreset: "medium", + wallpaper: "/wallpapers/does-not-exist.jpg", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + }); + + await expect(exporter.export()).rejects.toBeInstanceOf(BackgroundLoadError); + }); }); diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 46ac6a0..f073d6b 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -8,6 +8,7 @@ import type { WebcamSizePreset, ZoomRegion, } from "@/components/video-editor/types"; +import { BackgroundLoadError } from "@/lib/wallpaper"; import { getPlatform } from "@/utils/platformUtils"; import { AsyncVideoFrameQueue } from "./asyncVideoFrameQueue"; import { FrameRenderer } from "./frameRenderer"; @@ -326,6 +327,9 @@ export class GifExporter { return { success: true, blob }; } catch (error) { + if (error instanceof BackgroundLoadError) { + throw error; + } console.error("GIF Export error:", error); return { success: false, diff --git a/src/lib/exporter/videoExporter.browser.test.ts b/src/lib/exporter/videoExporter.browser.test.ts index ec2b0f6..ae8c7bc 100644 --- a/src/lib/exporter/videoExporter.browser.test.ts +++ b/src/lib/exporter/videoExporter.browser.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url"; +import { BackgroundLoadError } from "../wallpaper"; import type { ExportProgress } from "./types"; import { VideoExporter } from "./videoExporter"; @@ -40,4 +41,42 @@ describe("VideoExporter (real browser)", () => { expect(finalizing.length).toBeGreaterThan(0); expect(finalizing.at(-1)!.percentage).toBe(100); }); + + it("exports successfully with an image wallpaper (served by Vite dev server)", async () => { + const exporter = new VideoExporter({ + videoUrl: sampleVideoUrl, + width: 320, + height: 180, + frameRate: 15, + bitrate: 1_000_000, + wallpaper: "/wallpapers/wallpaper1.jpg", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + }); + + const result = await exporter.export(); + expect(result.success, result.error).toBe(true); + expect(result.blob!.size).toBeGreaterThan(1024); + }); + + it("throws BackgroundLoadError when wallpaper fails to load (no silent black fallback)", async () => { + const exporter = new VideoExporter({ + videoUrl: sampleVideoUrl, + width: 320, + height: 180, + frameRate: 15, + bitrate: 1_000_000, + wallpaper: "/wallpapers/does-not-exist.jpg", + zoomRegions: [], + showShadow: false, + shadowIntensity: 0, + showBlur: false, + cropRegion: { x: 0, y: 0, width: 1, height: 1 }, + }); + + await expect(exporter.export()).rejects.toBeInstanceOf(BackgroundLoadError); + }); }); diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 44c1b88..0ea1ec6 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -7,6 +7,7 @@ import type { WebcamSizePreset, ZoomRegion, } from "@/components/video-editor/types"; +import { BackgroundLoadError } from "@/lib/wallpaper"; import { getPlatform } from "@/utils/platformUtils"; import { AsyncVideoFrameQueue } from "./asyncVideoFrameQueue"; import { AudioProcessor } from "./audioEncoder"; @@ -82,6 +83,11 @@ export class VideoExporter { return { success: false, error: "Export cancelled" }; } + if (normalizedError instanceof BackgroundLoadError) { + this.cleanup(); + throw normalizedError; + } + if (encoderPreferences.length > 1) { console.warn( `[VideoExporter] ${encoderPreference} export attempt failed:`, diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts new file mode 100644 index 0000000..dbb5e3c --- /dev/null +++ b/src/lib/wallpaper.test.ts @@ -0,0 +1,167 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + BackgroundLoadError, + classifyWallpaper, + DEFAULT_WALLPAPER, + resolveImageWallpaperUrl, +} from "./wallpaper"; + +describe("classifyWallpaper", () => { + it("classifies hex color", () => { + expect(classifyWallpaper("#1a1a2e")).toEqual({ kind: "color", value: "#1a1a2e" }); + }); + + it("classifies linear gradient", () => { + const value = "linear-gradient(90deg, red, blue)"; + expect(classifyWallpaper(value)).toEqual({ kind: "gradient", value }); + }); + + it("classifies radial gradient", () => { + const value = "radial-gradient(circle, red, blue)"; + expect(classifyWallpaper(value)).toEqual({ kind: "gradient", value }); + }); + + it("classifies leading-slash image path", () => { + expect(classifyWallpaper("/wallpapers/wallpaper1.jpg")).toEqual({ + kind: "image", + path: "/wallpapers/wallpaper1.jpg", + }); + }); + + it("classifies http URL as image", () => { + expect(classifyWallpaper("https://example.com/bg.jpg")).toEqual({ + kind: "image", + path: "https://example.com/bg.jpg", + }); + }); + + it("classifies file:// URL as image", () => { + expect(classifyWallpaper("file:///tmp/bg.jpg")).toEqual({ + kind: "image", + path: "file:///tmp/bg.jpg", + }); + }); + + it("classifies data URI as image", () => { + expect(classifyWallpaper("data:image/png;base64,AAA")).toEqual({ + kind: "image", + path: "data:image/png;base64,AAA", + }); + }); + + it("DEFAULT_WALLPAPER classifies as image", () => { + expect(classifyWallpaper(DEFAULT_WALLPAPER)).toEqual({ + kind: "image", + path: DEFAULT_WALLPAPER, + }); + }); +}); + +describe("resolveImageWallpaperUrl", () => { + beforeEach(() => { + vi.stubGlobal("window", { + ...globalThis.window, + location: { protocol: "http:" }, + electronAPI: undefined, + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("passes through http URL unchanged", async () => { + expect(await resolveImageWallpaperUrl("http://example.com/bg.jpg")).toBe( + "http://example.com/bg.jpg", + ); + }); + + it("passes through https URL unchanged", async () => { + expect(await resolveImageWallpaperUrl("https://example.com/bg.jpg")).toBe( + "https://example.com/bg.jpg", + ); + }); + + it("passes through file:// URL unchanged", async () => { + expect(await resolveImageWallpaperUrl("file:///tmp/bg.jpg")).toBe("file:///tmp/bg.jpg"); + }); + + it("passes through data URI unchanged", async () => { + const uri = "data:image/png;base64,AAAA"; + expect(await resolveImageWallpaperUrl(uri)).toBe(uri); + }); + + it("resolves leading-slash path via http dev server fallback", async () => { + expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + "/wallpapers/wallpaper1.jpg", + ); + }); + + it("resolves bare relative path via http dev server fallback", async () => { + expect(await resolveImageWallpaperUrl("wallpapers/wallpaper1.jpg")).toBe( + "/wallpapers/wallpaper1.jpg", + ); + }); + + it("encodes path segments with special characters", async () => { + expect(await resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe( + "/wallpapers/my%20image.jpg", + ); + }); + + it("resolves via electronAPI when not http protocol", async () => { + vi.stubGlobal("window", { + ...globalThis.window, + location: { protocol: "file:" }, + electronAPI: { + getAssetBasePath: vi.fn().mockResolvedValue("file:///opt/app/public/"), + }, + }); + expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + "file:///opt/app/public/wallpapers/wallpaper1.jpg", + ); + }); + + it("electronAPI branch appends trailing slash to base if missing", async () => { + vi.stubGlobal("window", { + ...globalThis.window, + location: { protocol: "file:" }, + electronAPI: { + getAssetBasePath: vi.fn().mockResolvedValue("file:///opt/app/public"), + }, + }); + expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + "file:///opt/app/public/wallpapers/wallpaper1.jpg", + ); + }); + + it("falls back to leading-slash relative when electronAPI returns null", async () => { + vi.stubGlobal("window", { + ...globalThis.window, + location: { protocol: "file:" }, + electronAPI: { + getAssetBasePath: vi.fn().mockResolvedValue(null), + }, + }); + expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + "/wallpapers/wallpaper1.jpg", + ); + }); +}); + +describe("BackgroundLoadError", () => { + it("carries the failing URL and is instanceof Error", () => { + const err = new BackgroundLoadError("file:///missing.jpg"); + expect(err).toBeInstanceOf(Error); + expect(err).toBeInstanceOf(BackgroundLoadError); + expect(err.url).toBe("file:///missing.jpg"); + expect(err.name).toBe("BackgroundLoadError"); + expect(err.message).toContain("file:///missing.jpg"); + }); + + it("preserves cause when provided", () => { + const cause = new Error("inner"); + const err = new BackgroundLoadError("file:///missing.jpg", cause); + expect(err.cause).toBe(cause); + }); +}); diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts new file mode 100644 index 0000000..f949177 --- /dev/null +++ b/src/lib/wallpaper.ts @@ -0,0 +1,43 @@ +import { getAssetPath } from "@/lib/assetPath"; + +export const DEFAULT_WALLPAPER = "/wallpapers/wallpaper1.jpg"; + +export type WallpaperClassification = + | { kind: "color"; value: string } + | { kind: "gradient"; value: string } + | { kind: "image"; path: string }; + +export function classifyWallpaper(value: string): WallpaperClassification { + if (value.startsWith("#")) { + return { kind: "color", value }; + } + if (value.startsWith("linear-gradient") || value.startsWith("radial-gradient")) { + return { kind: "gradient", value }; + } + return { kind: "image", path: value }; +} + +export async function resolveImageWallpaperUrl(imagePath: string): Promise { + if ( + imagePath.startsWith("http://") || + imagePath.startsWith("https://") || + imagePath.startsWith("file://") || + imagePath.startsWith("data:") + ) { + return imagePath; + } + const relative = imagePath.replace(/^\/+/, ""); + return getAssetPath(relative); +} + +export class BackgroundLoadError extends Error { + readonly url: string; + readonly cause?: unknown; + + constructor(url: string, cause?: unknown) { + super(`Failed to load background image: ${url}`); + this.name = "BackgroundLoadError"; + this.url = url; + this.cause = cause; + } +} From adf3855ac89d188c8b94ffc9c7dd6f4779460abd Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 18:16:57 -0500 Subject: [PATCH 134/152] harden wallpaper resolver against traversal, PII, and SSOT drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversarial review surfaced four defects and four drive-bys. All applied: B1 (security, MEDIUM) — Path traversal via encodeRelativeAssetPath. encodeURIComponent passed "." and ".." through unchanged; percent-encoded "%2e%2e" got decoded by the URL constructor. Either form escaped the asset root: new URL("../../etc/passwd", "file:///opt/Openscreen/resources/") → file:///opt/etc/passwd. Reject both at src/lib/assetPath.ts via a new UnsafeAssetPathError thrown when a decoded segment equals "." or "..". B2 (correctness) — classifyWallpaper returned { kind: "image" } for conic-gradient(...), rgb(...), hsl(...), oklch(...), empty string, and named colors like "red". Old frameRenderer's bare fillStyle = value handled these; new code would throw BackgroundLoadError with misleading message. Classification now anchors on regexes, accepts all CSS color functions and all three gradient types, treats unknown strings as fallthrough color (old behavior), and normalizes "" to "#000000". B3 (SSOT) — DEFAULT_WALLPAPER, projectPersistence.WALLPAPER_PATHS, and SettingsPanel.WALLPAPER_RELATIVE independently hardcoded the same /wallpapers/wallpaperN.jpg pattern. Three drift sites collapse into one: WALLPAPER_PATHS lives in src/lib/wallpaper.ts, DEFAULT_WALLPAPER derives from WALLPAPER_PATHS[0], projectPersistence re-exports from the canonical module, SettingsPanel imports it directly. B4 (privacy) — BackgroundLoadError.message and the translated toast surfaced full file paths like file:///home//…/wallpaper.jpg — leaks the user's home directory in copy-pasted bug reports. Added a displayUrl getter that returns just the basename (or "data:…" for data URIs), wired into the toast. Full URL remains in console.error and error.url for debugging. N1 — resolveImageWallpaperUrl now rejects image paths that don't live under /wallpapers/ (throws BackgroundLoadError). Narrows the blast radius of the returned / base so the renderer can only request files within the wallpapers directory, regardless of what the project JSON claims. N2 — videoExporter retry loop no longer calls cleanup() twice in the BackgroundLoadError branch; the finally handles it. N3 — Browser tests assert BackgroundLoadError.url contains the failing path. Guards the {{url}} i18n interpolation contract. N4 — VideoPlayback wallpaper resolve effect now catches resolver throws (UnsafeAssetPathError, BackgroundLoadError from /wallpapers/ prefix enforcement). Prevents the new strict-rejection logic from silently leaving the preview without a background. Tests: 35 unit tests pass (up from 20); new coverage for all color functions, all gradient types, empty string, named color fallback, whitespace trimming, /wallpapers/ prefix enforcement, traversal rejection, percent-encoded traversal rejection, displayUrl basename and data-URI abbreviation. --- src/components/video-editor/SettingsPanel.tsx | 84 ++++++------ src/components/video-editor/VideoEditor.tsx | 2 +- src/components/video-editor/VideoPlayback.tsx | 9 +- .../video-editor/projectPersistence.ts | 8 +- src/lib/assetPath.ts | 23 +++- src/lib/exporter/gifExporter.browser.test.ts | 6 +- .../exporter/videoExporter.browser.test.ts | 6 +- src/lib/exporter/videoExporter.ts | 1 - src/lib/wallpaper.test.ts | 128 ++++++++++++++---- src/lib/wallpaper.ts | 62 +++++++-- 10 files changed, 235 insertions(+), 94 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 4fb4193..ec5ad0d 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -34,11 +34,11 @@ import { Slider } from "@/components/ui/slider"; import { Switch } from "@/components/ui/switch"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useScopedT } from "@/contexts/I18nContext"; -import { getAssetPath } from "@/lib/assetPath"; import { WEBCAM_LAYOUT_PRESETS } from "@/lib/compositeLayout"; import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter"; import { GIF_FRAME_RATES, GIF_SIZE_PRESETS } from "@/lib/exporter"; import { cn } from "@/lib/utils"; +import { resolveImageWallpaperUrl, WALLPAPER_PATHS } from "@/lib/wallpaper"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; @@ -123,11 +123,6 @@ function CustomSpeedInput({ ); } -const WALLPAPER_COUNT = 18; -const WALLPAPER_RELATIVE = Array.from( - { length: WALLPAPER_COUNT }, - (_, i) => `wallpapers/wallpaper${i + 1}.jpg`, -); const GRADIENTS = [ "linear-gradient( 111.6deg, rgba(114,167,232,1) 9.4%, rgba(253,129,82,1) 43.9%, rgba(253,129,82,1) 54.8%, rgba(249,202,86,1) 86.3% )", "linear-gradient(120deg, #d4fc79 0%, #96e6a1 100%)", @@ -334,10 +329,10 @@ export function SettingsPanel({ let mounted = true; (async () => { try { - const resolved = await Promise.all(WALLPAPER_RELATIVE.map((p) => getAssetPath(p))); + const resolved = await Promise.all(WALLPAPER_PATHS.map((p) => resolveImageWallpaperUrl(p))); if (mounted) setWallpaperPaths(resolved); } catch (_err) { - if (mounted) setWallpaperPaths(WALLPAPER_RELATIVE.map((p) => `/${p}`)); + if (mounted) setWallpaperPaths([...WALLPAPER_PATHS]); } })(); return () => { @@ -526,7 +521,7 @@ export function SettingsPanel({ setCustomImages((prev) => prev.filter((img) => img !== imageUrl)); // If the removed image was selected, clear selection if (selected === imageUrl) { - onWallpaperChange(wallpaperPaths[0] || WALLPAPER_RELATIVE[0]); + onWallpaperChange(wallpaperPaths[0] || WALLPAPER_PATHS[0]); } }; @@ -1146,42 +1141,41 @@ export function SettingsPanel({ ); })} - {(wallpaperPaths.length > 0 - ? wallpaperPaths - : WALLPAPER_RELATIVE.map((p) => `/${p}`) - ).map((path) => { - const isSelected = (() => { - if (!selected) return false; - if (selected === path) return true; - try { - const clean = (s: string) => - s.replace(/^file:\/\//, "").replace(/^\//, ""); - if (clean(selected).endsWith(clean(path))) return true; - if (clean(path).endsWith(clean(selected))) return true; - } catch { - // Best-effort comparison; fallback to strict match. - } - return false; - })(); - return ( -
onWallpaperChange(path)} - role="button" - /> - ); - })} + {(wallpaperPaths.length > 0 ? wallpaperPaths : WALLPAPER_PATHS).map( + (path) => { + const isSelected = (() => { + if (!selected) return false; + if (selected === path) return true; + try { + const clean = (s: string) => + s.replace(/^file:\/\//, "").replace(/^\//, ""); + if (clean(selected).endsWith(clean(path))) return true; + if (clean(path).endsWith(clean(selected))) return true; + } catch { + // Best-effort comparison; fallback to strict match. + } + return false; + })(); + return ( +
onWallpaperChange(path)} + role="button" + /> + ); + }, + )}
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 3694d9e..0a03bf1 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -1568,7 +1568,7 @@ export default function VideoEditor() { } catch (error) { console.error("Export error:", error); if (error instanceof BackgroundLoadError) { - const message = t("errors.exportBackgroundLoadFailed", { url: error.url }); + const message = t("errors.exportBackgroundLoadFailed", { url: error.displayUrl }); setExportError(message); toast.error(message); } else { diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index db69310..d356012 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1185,8 +1185,13 @@ const VideoPlayback = forwardRef( if (mounted) setResolvedWallpaper(classified.value); return; } - const resolved = await resolveImageWallpaperUrl(classified.path); - if (mounted) setResolvedWallpaper(resolved); + try { + const resolved = await resolveImageWallpaperUrl(classified.path); + if (mounted) setResolvedWallpaper(resolved); + } catch (err) { + console.warn("[VideoPlayback] wallpaper resolve failed:", err); + if (mounted) setResolvedWallpaper(null); + } })(); return () => { mounted = false; diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index f9640e4..6d8f689 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -2,7 +2,7 @@ import { normalizeBlurColor, normalizeBlurType } from "@/lib/blurEffects"; import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@/lib/exporter"; import type { ProjectMedia } from "@/lib/recordingSession"; import { normalizeProjectMedia } from "@/lib/recordingSession"; -import { DEFAULT_WALLPAPER } from "@/lib/wallpaper"; +import { DEFAULT_WALLPAPER, WALLPAPER_PATHS } from "@/lib/wallpaper"; import { ASPECT_RATIOS, type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { type AnnotationRegion, @@ -38,13 +38,9 @@ import { type ZoomRegion, } from "./types"; -const WALLPAPER_COUNT = 18; const VALID_BLUR_SHAPES = new Set(["rectangle", "oval", "freehand"] as const); -export const WALLPAPER_PATHS = Array.from( - { length: WALLPAPER_COUNT }, - (_, i) => `/wallpapers/wallpaper${i + 1}.jpg`, -); +export { WALLPAPER_PATHS }; export const PROJECT_VERSION = 2; diff --git a/src/lib/assetPath.ts b/src/lib/assetPath.ts index 8188de5..7ba1015 100644 --- a/src/lib/assetPath.ts +++ b/src/lib/assetPath.ts @@ -1,9 +1,22 @@ +export class UnsafeAssetPathError extends Error { + constructor(segment: string) { + super(`Unsafe asset path segment: ${segment}`); + this.name = "UnsafeAssetPathError"; + } +} + function encodeRelativeAssetPath(relativePath: string): string { return relativePath .replace(/^\/+/, "") .split("/") .filter(Boolean) - .map((part) => encodeURIComponent(part)) + .map((part) => { + const decoded = decodeURIComponent(part); + if (decoded === "." || decoded === "..") { + throw new UnsafeAssetPathError(decoded); + } + return encodeURIComponent(decoded); + }) .join("/"); } @@ -16,7 +29,6 @@ export async function getAssetPath(relativePath: string): Promise { try { if (typeof window !== "undefined") { - // If running in a dev server (http/https), prefer the web-served path if ( window.location && window.location.protocol && @@ -32,11 +44,12 @@ export async function getAssetPath(relativePath: string): Promise { } } } - } catch { - // ignore and use fallback + } catch (err) { + if (err instanceof UnsafeAssetPathError) { + throw err; + } } - // Fallback for web/dev server: public/wallpapers are served at '/wallpapers/...' return `/${encodedRelativePath}`; } diff --git a/src/lib/exporter/gifExporter.browser.test.ts b/src/lib/exporter/gifExporter.browser.test.ts index 5a69468..1d96076 100644 --- a/src/lib/exporter/gifExporter.browser.test.ts +++ b/src/lib/exporter/gifExporter.browser.test.ts @@ -79,6 +79,10 @@ describe("GifExporter (real browser)", () => { cropRegion: { x: 0, y: 0, width: 1, height: 1 }, }); - await expect(exporter.export()).rejects.toBeInstanceOf(BackgroundLoadError); + const rejection = exporter.export(); + await expect(rejection).rejects.toBeInstanceOf(BackgroundLoadError); + await expect(rejection).rejects.toMatchObject({ + url: expect.stringContaining("does-not-exist"), + }); }); }); diff --git a/src/lib/exporter/videoExporter.browser.test.ts b/src/lib/exporter/videoExporter.browser.test.ts index ae8c7bc..cca896f 100644 --- a/src/lib/exporter/videoExporter.browser.test.ts +++ b/src/lib/exporter/videoExporter.browser.test.ts @@ -77,6 +77,10 @@ describe("VideoExporter (real browser)", () => { cropRegion: { x: 0, y: 0, width: 1, height: 1 }, }); - await expect(exporter.export()).rejects.toBeInstanceOf(BackgroundLoadError); + const rejection = exporter.export(); + await expect(rejection).rejects.toBeInstanceOf(BackgroundLoadError); + await expect(rejection).rejects.toMatchObject({ + url: expect.stringContaining("does-not-exist"), + }); }); }); diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 0ea1ec6..cc8b7cf 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -84,7 +84,6 @@ export class VideoExporter { } if (normalizedError instanceof BackgroundLoadError) { - this.cleanup(); throw normalizedError; } diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts index dbb5e3c..f4fe08e 100644 --- a/src/lib/wallpaper.test.ts +++ b/src/lib/wallpaper.test.ts @@ -1,54 +1,109 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { UnsafeAssetPathError } from "./assetPath"; import { BackgroundLoadError, classifyWallpaper, DEFAULT_WALLPAPER, resolveImageWallpaperUrl, + WALLPAPER_COUNT, + WALLPAPER_PATHS, } from "./wallpaper"; +describe("WALLPAPER_PATHS", () => { + it("contains WALLPAPER_COUNT entries", () => { + expect(WALLPAPER_PATHS).toHaveLength(WALLPAPER_COUNT); + }); + + it("DEFAULT_WALLPAPER is WALLPAPER_PATHS[0]", () => { + expect(DEFAULT_WALLPAPER).toBe(WALLPAPER_PATHS[0]); + }); +}); + describe("classifyWallpaper", () => { - it("classifies hex color", () => { + it("hex color", () => { expect(classifyWallpaper("#1a1a2e")).toEqual({ kind: "color", value: "#1a1a2e" }); }); - it("classifies linear gradient", () => { - const value = "linear-gradient(90deg, red, blue)"; - expect(classifyWallpaper(value)).toEqual({ kind: "gradient", value }); + it("rgb() color", () => { + expect(classifyWallpaper("rgb(1, 2, 3)")).toEqual({ kind: "color", value: "rgb(1, 2, 3)" }); }); - it("classifies radial gradient", () => { - const value = "radial-gradient(circle, red, blue)"; - expect(classifyWallpaper(value)).toEqual({ kind: "gradient", value }); + it("rgba() color", () => { + expect(classifyWallpaper("rgba(1, 2, 3, 0.5)")).toEqual({ + kind: "color", + value: "rgba(1, 2, 3, 0.5)", + }); }); - it("classifies leading-slash image path", () => { + it("hsl() color", () => { + expect(classifyWallpaper("hsl(180, 50%, 50%)")).toEqual({ + kind: "color", + value: "hsl(180, 50%, 50%)", + }); + }); + + it("oklch() color", () => { + expect(classifyWallpaper("oklch(50% 0.1 180)")).toEqual({ + kind: "color", + value: "oklch(50% 0.1 180)", + }); + }); + + it("linear gradient", () => { + const v = "linear-gradient(90deg, red, blue)"; + expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); + }); + + it("radial gradient", () => { + const v = "radial-gradient(circle, red, blue)"; + expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); + }); + + it("conic gradient", () => { + const v = "conic-gradient(red, blue)"; + expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); + }); + + it("leading-slash image path", () => { expect(classifyWallpaper("/wallpapers/wallpaper1.jpg")).toEqual({ kind: "image", path: "/wallpapers/wallpaper1.jpg", }); }); - it("classifies http URL as image", () => { + it("http URL as image", () => { expect(classifyWallpaper("https://example.com/bg.jpg")).toEqual({ kind: "image", path: "https://example.com/bg.jpg", }); }); - it("classifies file:// URL as image", () => { + it("file:// URL as image", () => { expect(classifyWallpaper("file:///tmp/bg.jpg")).toEqual({ kind: "image", path: "file:///tmp/bg.jpg", }); }); - it("classifies data URI as image", () => { + it("data URI as image", () => { expect(classifyWallpaper("data:image/png;base64,AAA")).toEqual({ kind: "image", path: "data:image/png;base64,AAA", }); }); + it("named color falls back to color", () => { + expect(classifyWallpaper("red")).toEqual({ kind: "color", value: "red" }); + }); + + it("empty string falls back to black", () => { + expect(classifyWallpaper("")).toEqual({ kind: "color", value: "#000000" }); + }); + + it("trims whitespace", () => { + expect(classifyWallpaper(" #abcdef ")).toEqual({ kind: "color", value: "#abcdef" }); + }); + it("DEFAULT_WALLPAPER classifies as image", () => { expect(classifyWallpaper(DEFAULT_WALLPAPER)).toEqual({ kind: "image", @@ -70,46 +125,64 @@ describe("resolveImageWallpaperUrl", () => { vi.unstubAllGlobals(); }); - it("passes through http URL unchanged", async () => { + it("passes through http URL", async () => { expect(await resolveImageWallpaperUrl("http://example.com/bg.jpg")).toBe( "http://example.com/bg.jpg", ); }); - it("passes through https URL unchanged", async () => { + it("passes through https URL", async () => { expect(await resolveImageWallpaperUrl("https://example.com/bg.jpg")).toBe( "https://example.com/bg.jpg", ); }); - it("passes through file:// URL unchanged", async () => { + it("passes through file:// URL", async () => { expect(await resolveImageWallpaperUrl("file:///tmp/bg.jpg")).toBe("file:///tmp/bg.jpg"); }); - it("passes through data URI unchanged", async () => { + it("passes through data URI", async () => { const uri = "data:image/png;base64,AAAA"; expect(await resolveImageWallpaperUrl(uri)).toBe(uri); }); - it("resolves leading-slash path via http dev server fallback", async () => { + it("resolves leading-slash wallpaper path via http fallback", async () => { expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( "/wallpapers/wallpaper1.jpg", ); }); - it("resolves bare relative path via http dev server fallback", async () => { + it("resolves bare relative wallpaper path", async () => { expect(await resolveImageWallpaperUrl("wallpapers/wallpaper1.jpg")).toBe( "/wallpapers/wallpaper1.jpg", ); }); - it("encodes path segments with special characters", async () => { + it("encodes special characters in path segments", async () => { expect(await resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe( "/wallpapers/my%20image.jpg", ); }); - it("resolves via electronAPI when not http protocol", async () => { + it("rejects image paths outside /wallpapers/", async () => { + await expect(resolveImageWallpaperUrl("/etc/passwd")).rejects.toBeInstanceOf( + BackgroundLoadError, + ); + }); + + it("rejects traversal attempts", async () => { + await expect(resolveImageWallpaperUrl("/wallpapers/../etc/passwd")).rejects.toBeInstanceOf( + UnsafeAssetPathError, + ); + }); + + it("rejects percent-encoded traversal", async () => { + await expect(resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar")).rejects.toBeInstanceOf( + UnsafeAssetPathError, + ); + }); + + it("resolves via electronAPI when not http", async () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, @@ -122,7 +195,7 @@ describe("resolveImageWallpaperUrl", () => { ); }); - it("electronAPI branch appends trailing slash to base if missing", async () => { + it("electronAPI branch appends trailing slash if missing", async () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, @@ -151,12 +224,21 @@ describe("resolveImageWallpaperUrl", () => { describe("BackgroundLoadError", () => { it("carries the failing URL and is instanceof Error", () => { - const err = new BackgroundLoadError("file:///missing.jpg"); + const err = new BackgroundLoadError("/home/user/secret/wallpaper.jpg"); expect(err).toBeInstanceOf(Error); expect(err).toBeInstanceOf(BackgroundLoadError); - expect(err.url).toBe("file:///missing.jpg"); + expect(err.url).toBe("/home/user/secret/wallpaper.jpg"); expect(err.name).toBe("BackgroundLoadError"); - expect(err.message).toContain("file:///missing.jpg"); + }); + + it("displayUrl hides parent directories to avoid leaking PII", () => { + const err = new BackgroundLoadError("file:///home/enrique/projects/openscreen/wallpaper1.jpg"); + expect(err.displayUrl).toBe("wallpaper1.jpg"); + }); + + it("displayUrl abbreviates data URIs", () => { + const err = new BackgroundLoadError("data:image/png;base64,AAA"); + expect(err.displayUrl).toBe("data:…"); }); it("preserves cause when provided", () => { diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index f949177..449d2e2 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -1,22 +1,42 @@ import { getAssetPath } from "@/lib/assetPath"; -export const DEFAULT_WALLPAPER = "/wallpapers/wallpaper1.jpg"; +export const WALLPAPER_COUNT = 18; + +export const WALLPAPER_PATHS: readonly string[] = Array.from( + { length: WALLPAPER_COUNT }, + (_, i) => `/wallpapers/wallpaper${i + 1}.jpg`, +); + +export const DEFAULT_WALLPAPER = WALLPAPER_PATHS[0]; export type WallpaperClassification = | { kind: "color"; value: string } | { kind: "gradient"; value: string } | { kind: "image"; path: string }; +const GRADIENT_RE = /^(linear|radial|conic)-gradient\(/; +const COLOR_FUNC_RE = /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(/; +const IMAGE_URL_RE = /^(\/|https?:\/\/|file:\/\/|data:)/; + export function classifyWallpaper(value: string): WallpaperClassification { - if (value.startsWith("#")) { - return { kind: "color", value }; + const trimmed = value.trim(); + if (trimmed === "") { + return { kind: "color", value: "#000000" }; } - if (value.startsWith("linear-gradient") || value.startsWith("radial-gradient")) { - return { kind: "gradient", value }; + if (trimmed.startsWith("#") || COLOR_FUNC_RE.test(trimmed)) { + return { kind: "color", value: trimmed }; } - return { kind: "image", path: value }; + if (GRADIENT_RE.test(trimmed)) { + return { kind: "gradient", value: trimmed }; + } + if (IMAGE_URL_RE.test(trimmed)) { + return { kind: "image", path: trimmed }; + } + return { kind: "color", value: trimmed }; } +const ALLOWED_IMAGE_PREFIX = "/wallpapers/"; + export async function resolveImageWallpaperUrl(imagePath: string): Promise { if ( imagePath.startsWith("http://") || @@ -26,8 +46,14 @@ export async function resolveImageWallpaperUrl(imagePath: string): Promise Date: Fri, 24 Apr 2026 18:22:27 -0500 Subject: [PATCH 135/152] avoid 404s on first swatch render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SettingsPanel fell back to rendering WALLPAPER_PATHS (raw /wallpapers/*.jpg strings) during the brief window before the resolveImageWallpaperUrl effect populated wallpaperPaths. In packaged Electron the browser resolved those against a file:// origin, producing 18 ERR_FILE_NOT_FOUND requests per load / reload. The second render replaced them with correct URLs, so swatches appeared — but the wasted requests showed up in devtools and churned the network panel. Drop the fallback; render nothing until the effect completes. The resolution is effectively instant and avoids the empty-origin round trip. --- src/components/video-editor/SettingsPanel.tsx | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index ec5ad0d..4ebdf33 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1141,41 +1141,39 @@ export function SettingsPanel({ ); })} - {(wallpaperPaths.length > 0 ? wallpaperPaths : WALLPAPER_PATHS).map( - (path) => { - const isSelected = (() => { - if (!selected) return false; - if (selected === path) return true; - try { - const clean = (s: string) => - s.replace(/^file:\/\//, "").replace(/^\//, ""); - if (clean(selected).endsWith(clean(path))) return true; - if (clean(path).endsWith(clean(selected))) return true; - } catch { - // Best-effort comparison; fallback to strict match. - } - return false; - })(); - return ( -
onWallpaperChange(path)} - role="button" - /> - ); - }, - )} + {wallpaperPaths.map((path) => { + const isSelected = (() => { + if (!selected) return false; + if (selected === path) return true; + try { + const clean = (s: string) => + s.replace(/^file:\/\//, "").replace(/^\//, ""); + if (clean(selected).endsWith(clean(path))) return true; + if (clean(path).endsWith(clean(selected))) return true; + } catch { + // Best-effort comparison; fallback to strict match. + } + return false; + })(); + return ( +
onWallpaperChange(path)} + role="button" + /> + ); + })}
From 702b7330744f1a4f9b66eae95a2d597841381720 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 18:33:03 -0500 Subject: [PATCH 136/152] resolve asset base path synchronously from preload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every consumer of /wallpapers/*.jpg — SettingsPanel, VideoPlayback, frameRenderer — was doing async IPC round trips, useEffect dances, and Promise.all for a value that is a build-time constant per process. Each consumer showed briefly-empty or briefly-404ing state on first paint until the handler's reply resolved. The asset base URL depends only on process.defaultApp and process.resourcesPath / __dirname — all available in preload at context-bridge time. Compute once there, expose as a sync string. - preload.ts resolves baseDir (process.resourcesPath packaged, /public unpackaged) and emits assetBaseUrl synchronously. - get-asset-base-path IPC handler + main-process branching deleted. - getAssetPath() is now sync. Returns string, not Promise. Throws AssetBaseUnavailableError (new) when electronAPI.assetBaseUrl is missing — catastrophic preload failure, not silent 404. - resolveImageWallpaperUrl() sync; same sync throw semantics. - SettingsPanel: Promise.all + useState + useEffect collapse to one useMemo. First paint has real URLs, no 18× ERR_FILE_NOT_FOUND, no flicker. - VideoPlayback: wallpaper-resolve useEffect collapses to useMemo. - frameRenderer.setupBackground: drops the await. - electronAPI type decls updated in both .d.ts files. - 35 unit tests updated to reflect sync signature + new AssetBaseUnavailableError contract. Silent-fallback behavior from getAssetPath (returning /relative when electronAPI failed) is gone. Renderers now surface preload failures instead of rendering 404s. --- electron/electron-env.d.ts | 2 +- electron/ipc/handlers.ts | 18 +---- electron/preload.ts | 18 ++++- src/components/video-editor/SettingsPanel.tsx | 19 +---- src/components/video-editor/VideoPlayback.tsx | 34 +++------ src/lib/assetPath.ts | 44 +++++------ src/lib/exporter/frameRenderer.ts | 2 +- src/lib/wallpaper.test.ts | 74 ++++++++----------- src/lib/wallpaper.ts | 2 +- src/vite-env.d.ts | 2 +- 10 files changed, 83 insertions(+), 132 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index e20cf7f..dff8029 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -37,7 +37,7 @@ interface Window { status: string; error?: string; }>; - getAssetBasePath: () => Promise; + assetBaseUrl: string; storeRecordedVideo: ( videoData: ArrayBuffer, fileName: string, diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 6aec971..2b72241 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; +import { fileURLToPath } from "node:url"; import { app, BrowserWindow, @@ -801,22 +801,6 @@ export function registerIpcHandlers( } }); - // Return base path for assets so renderer can resolve file:// paths in production. - // Packaged: electron-builder extraResources copies public/wallpapers -> resources/wallpapers. - // Unpackaged: wallpapers live at /public/wallpapers. - // Single convention: "/wallpapers/x.jpg" resolves in both modes. - ipcMain.handle("get-asset-base-path", () => { - try { - const baseDir = app.isPackaged - ? process.resourcesPath - : path.join(app.getAppPath(), "public"); - return pathToFileURL(`${baseDir}${path.sep}`).toString(); - } catch (err) { - console.error("Failed to resolve asset base path:", err); - return null; - } - }); - /** * Handles saving an exported video file. * Shows a save dialog, normalizes the file path for the current OS, diff --git a/electron/preload.ts b/electron/preload.ts index 6aa066f..ec5181c 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -1,17 +1,27 @@ +import path from "node:path"; +import { pathToFileURL } from "node:url"; import { contextBridge, ipcRenderer } from "electron"; import type { RecordingSession, StoreRecordedSessionInput } from "../src/lib/recordingSession"; +// Asset base URL is a build-time constant per process; resolve once here so +// the renderer can consume it synchronously. Packaged: electron-builder +// extraResources copies public/wallpapers -> resources/wallpapers (see +// electron-builder.json5). Unpackaged: wallpapers live at /public/, +// and __dirname in dist-electron resolves to /dist-electron/. +const isPackagedProcess = !process.defaultApp; +const assetBaseDir = isPackagedProcess + ? process.resourcesPath + : path.join(__dirname, "..", "public"); +const assetBaseUrl = pathToFileURL(`${assetBaseDir}${path.sep}`).toString(); + contextBridge.exposeInMainWorld("electronAPI", { + assetBaseUrl, hudOverlayHide: () => { ipcRenderer.send("hud-overlay-hide"); }, hudOverlayClose: () => { ipcRenderer.send("hud-overlay-close"); }, - getAssetBasePath: async () => { - // ask main process for the correct base path (production vs dev) - return await ipcRenderer.invoke("get-asset-base-path"); - }, getSources: async (opts: Electron.SourcesOptions) => { return await ipcRenderer.invoke("get-sources", opts); }, diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 4ebdf33..91840bc 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -14,7 +14,7 @@ import { Upload, X, } from "lucide-react"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { Accordion, @@ -321,24 +321,9 @@ export function SettingsPanel({ onWebcamSizePresetCommit, }: SettingsPanelProps) { const t = useScopedT("settings"); - const [wallpaperPaths, setWallpaperPaths] = useState([]); + const wallpaperPaths = useMemo(() => WALLPAPER_PATHS.map(resolveImageWallpaperUrl), []); const [customImages, setCustomImages] = useState([]); const fileInputRef = useRef(null); - - useEffect(() => { - let mounted = true; - (async () => { - try { - const resolved = await Promise.all(WALLPAPER_PATHS.map((p) => resolveImageWallpaperUrl(p))); - if (mounted) setWallpaperPaths(resolved); - } catch (_err) { - if (mounted) setWallpaperPaths([...WALLPAPER_PATHS]); - } - })(); - return () => { - mounted = false; - }; - }, []); const colorPalette = [ "#FF0000", "#FFD700", diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index d356012..89a0a6c 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1108,7 +1108,17 @@ const VideoPlayback = forwardRef( videoReadyRafRef.current = requestAnimationFrame(waitForRenderableFrame); }; - const [resolvedWallpaper, setResolvedWallpaper] = useState(null); + const resolvedWallpaper = useMemo(() => { + const source = wallpaper || DEFAULT_WALLPAPER; + const classified = classifyWallpaper(source); + if (classified.kind !== "image") return classified.value; + try { + return resolveImageWallpaperUrl(classified.path); + } catch (err) { + console.warn("[VideoPlayback] wallpaper resolve failed:", err); + return null; + } + }, [wallpaper]); const webcamCssBoxShadow = useMemo( () => getWebcamLayoutCssBoxShadow(webcamLayoutPreset), [webcamLayoutPreset], @@ -1176,28 +1186,6 @@ const VideoPlayback = forwardRef( webcamVideo.currentTime = 0; }, [webcamVideoPath]); - useEffect(() => { - let mounted = true; - (async () => { - const source = wallpaper || DEFAULT_WALLPAPER; - const classified = classifyWallpaper(source); - if (classified.kind !== "image") { - if (mounted) setResolvedWallpaper(classified.value); - return; - } - try { - const resolved = await resolveImageWallpaperUrl(classified.path); - if (mounted) setResolvedWallpaper(resolved); - } catch (err) { - console.warn("[VideoPlayback] wallpaper resolve failed:", err); - if (mounted) setResolvedWallpaper(null); - } - })(); - return () => { - mounted = false; - }; - }, [wallpaper]); - useEffect(() => { return () => { if (videoReadyRafRef.current) { diff --git a/src/lib/assetPath.ts b/src/lib/assetPath.ts index 7ba1015..edba758 100644 --- a/src/lib/assetPath.ts +++ b/src/lib/assetPath.ts @@ -5,6 +5,13 @@ export class UnsafeAssetPathError extends Error { } } +export class AssetBaseUnavailableError extends Error { + constructor() { + super("electronAPI.assetBaseUrl is not available; preload did not load correctly"); + this.name = "AssetBaseUnavailableError"; + } +} + function encodeRelativeAssetPath(relativePath: string): string { return relativePath .replace(/^\/+/, "") @@ -24,33 +31,22 @@ function ensureTrailingSlash(value: string): string { return value.endsWith("/") ? value : `${value}/`; } -export async function getAssetPath(relativePath: string): Promise { - const encodedRelativePath = encodeRelativeAssetPath(relativePath); +export function getAssetPath(relativePath: string): string { + const encoded = encodeRelativeAssetPath(relativePath); - try { - if (typeof window !== "undefined") { - if ( - window.location && - window.location.protocol && - window.location.protocol.startsWith("http") - ) { - return `/${encodedRelativePath}`; - } - - if (window.electronAPI && typeof window.electronAPI.getAssetBasePath === "function") { - const base = await window.electronAPI.getAssetBasePath(); - if (base) { - return new URL(encodedRelativePath, ensureTrailingSlash(base)).toString(); - } - } - } - } catch (err) { - if (err instanceof UnsafeAssetPathError) { - throw err; - } + if (typeof window === "undefined") { + return `/${encoded}`; } - return `/${encodedRelativePath}`; + if (window.location?.protocol?.startsWith("http")) { + return `/${encoded}`; + } + + const base = window.electronAPI?.assetBaseUrl; + if (!base) { + throw new AssetBaseUnavailableError(); + } + return new URL(encoded, ensureTrailingSlash(base)).toString(); } export default getAssetPath; diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index e7362eb..20a2b2d 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -280,7 +280,7 @@ export class FrameRenderer { bgCtx.fillStyle = gradient; bgCtx.fillRect(0, 0, this.config.width, this.config.height); } else { - const imageUrl = await resolveImageWallpaperUrl(classified.path); + const imageUrl = resolveImageWallpaperUrl(classified.path); const img = new Image(); if (imageUrl.startsWith("http") && !imageUrl.startsWith(window.location.origin)) { img.crossOrigin = "anonymous"; diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts index f4fe08e..f1cb80b 100644 --- a/src/lib/wallpaper.test.ts +++ b/src/lib/wallpaper.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { UnsafeAssetPathError } from "./assetPath"; +import { AssetBaseUnavailableError, UnsafeAssetPathError } from "./assetPath"; import { BackgroundLoadError, classifyWallpaper, @@ -125,99 +125,87 @@ describe("resolveImageWallpaperUrl", () => { vi.unstubAllGlobals(); }); - it("passes through http URL", async () => { - expect(await resolveImageWallpaperUrl("http://example.com/bg.jpg")).toBe( - "http://example.com/bg.jpg", - ); + it("passes through http URL", () => { + expect(resolveImageWallpaperUrl("http://example.com/bg.jpg")).toBe("http://example.com/bg.jpg"); }); - it("passes through https URL", async () => { - expect(await resolveImageWallpaperUrl("https://example.com/bg.jpg")).toBe( + it("passes through https URL", () => { + expect(resolveImageWallpaperUrl("https://example.com/bg.jpg")).toBe( "https://example.com/bg.jpg", ); }); - it("passes through file:// URL", async () => { - expect(await resolveImageWallpaperUrl("file:///tmp/bg.jpg")).toBe("file:///tmp/bg.jpg"); + it("passes through file:// URL", () => { + expect(resolveImageWallpaperUrl("file:///tmp/bg.jpg")).toBe("file:///tmp/bg.jpg"); }); - it("passes through data URI", async () => { + it("passes through data URI", () => { const uri = "data:image/png;base64,AAAA"; - expect(await resolveImageWallpaperUrl(uri)).toBe(uri); + expect(resolveImageWallpaperUrl(uri)).toBe(uri); }); - it("resolves leading-slash wallpaper path via http fallback", async () => { - expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + it("resolves leading-slash wallpaper path via http fallback", () => { + expect(resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( "/wallpapers/wallpaper1.jpg", ); }); - it("resolves bare relative wallpaper path", async () => { - expect(await resolveImageWallpaperUrl("wallpapers/wallpaper1.jpg")).toBe( + it("resolves bare relative wallpaper path", () => { + expect(resolveImageWallpaperUrl("wallpapers/wallpaper1.jpg")).toBe( "/wallpapers/wallpaper1.jpg", ); }); - it("encodes special characters in path segments", async () => { - expect(await resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe( - "/wallpapers/my%20image.jpg", - ); + it("encodes special characters in path segments", () => { + expect(resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe("/wallpapers/my%20image.jpg"); }); - it("rejects image paths outside /wallpapers/", async () => { - await expect(resolveImageWallpaperUrl("/etc/passwd")).rejects.toBeInstanceOf( - BackgroundLoadError, - ); + it("rejects image paths outside /wallpapers/", () => { + expect(() => resolveImageWallpaperUrl("/etc/passwd")).toThrow(BackgroundLoadError); }); - it("rejects traversal attempts", async () => { - await expect(resolveImageWallpaperUrl("/wallpapers/../etc/passwd")).rejects.toBeInstanceOf( + it("rejects traversal attempts", () => { + expect(() => resolveImageWallpaperUrl("/wallpapers/../etc/passwd")).toThrow( UnsafeAssetPathError, ); }); - it("rejects percent-encoded traversal", async () => { - await expect(resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar")).rejects.toBeInstanceOf( + it("rejects percent-encoded traversal", () => { + expect(() => resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar")).toThrow( UnsafeAssetPathError, ); }); - it("resolves via electronAPI when not http", async () => { + it("resolves via electronAPI.assetBaseUrl when not http", () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, - electronAPI: { - getAssetBasePath: vi.fn().mockResolvedValue("file:///opt/app/public/"), - }, + electronAPI: { assetBaseUrl: "file:///opt/app/public/" }, }); - expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + expect(resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( "file:///opt/app/public/wallpapers/wallpaper1.jpg", ); }); - it("electronAPI branch appends trailing slash if missing", async () => { + it("appends trailing slash to assetBaseUrl if missing", () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, - electronAPI: { - getAssetBasePath: vi.fn().mockResolvedValue("file:///opt/app/public"), - }, + electronAPI: { assetBaseUrl: "file:///opt/app/public" }, }); - expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( + expect(resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( "file:///opt/app/public/wallpapers/wallpaper1.jpg", ); }); - it("falls back to leading-slash relative when electronAPI returns null", async () => { + it("throws loudly when assetBaseUrl is empty (no silent fallback)", () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, - electronAPI: { - getAssetBasePath: vi.fn().mockResolvedValue(null), - }, + electronAPI: { assetBaseUrl: "" }, }); - expect(await resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toBe( - "/wallpapers/wallpaper1.jpg", + expect(() => resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toThrow( + AssetBaseUnavailableError, ); }); }); diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index 449d2e2..c86ce43 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -37,7 +37,7 @@ export function classifyWallpaper(value: string): WallpaperClassification { const ALLOWED_IMAGE_PREFIX = "/wallpapers/"; -export async function resolveImageWallpaperUrl(imagePath: string): Promise { +export function resolveImageWallpaperUrl(imagePath: string): string { if ( imagePath.startsWith("http://") || imagePath.startsWith("https://") || diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index d76ee15..bdcb537 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -55,7 +55,7 @@ interface Window { message?: string; error?: string; }>; - getAssetBasePath: () => Promise; + assetBaseUrl: string; setRecordingState: (recording: boolean) => Promise; getCursorTelemetry: (videoPath?: string) => Promise<{ success: boolean; From f2ff7fb21cf38b018135f82da9f96692d645ae5c Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 18:55:04 -0500 Subject: [PATCH 137/152] address review audit: persist canonical wallpaper, dedupe types, tighten edge cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R1 — Persisted wallpaper is now always the canonical /wallpapers/wallpaperN.jpg form, never the resolved file:// URL. Swatch clicks pass WALLPAPER_PATHS[i] (the relative path) to onWallpaperChange; the resolved URL stays in wallpaperPreviewUrls for rendering only. This prevents machine-specific paths from being written into project JSON and avoids break-on-upgrade / break-on-share regressions. Legacy projects carrying resolved file:// URLs are rewritten by a new normalizer in normalizeProjectEditor: file://…(/assets)?/wallpapers/wallpaperN.jpg → /wallpapers/wallpaperN.jpg. R2 — resolveImageWallpaperUrl now catches anything getAssetPath throws (UnsafeAssetPathError, AssetBaseUnavailableError) and rewraps as BackgroundLoadError with the original as cause. Callers (videoExporter retry loop, gifExporter catch, VideoEditor toast) only need one instanceof check and users always see the translated errors.exportBackgroundLoadFailed toast. R3 — src/vite-env.d.ts no longer duplicates Window.electronAPI. The interface had drifted — renderer declaration was missing readBinaryFile, getPlatform, revealInFolder, getShortcuts, saveShortcuts, hudOverlay*, countdown overlay methods that electron-env.d.ts already declares. Removed the duplicate and kept the triple-slash reference so the authoritative declaration is the one in electron/electron-env.d.ts. N1 — GRADIENT_RE accepts optional "repeating-" prefix so repeating-linear/radial/conic-gradient values classify as gradients instead of falling through to color. N2 — displayBasename returns "(unknown)" sentinel for URLs without a meaningful basename (file:///, bare /) instead of leaking the original string. N3 — electron-builder.json5 extraResources block gets an inline comment pointing at preload.ts:assetBaseDir so the bidirectional coupling is discoverable from either file. Tests: 54 unit tests pass (up from 35). New coverage for repeating gradients, displayBasename sentinels, BackgroundLoadError cause wrapping, legacy file:// wallpaper normalization (5 cases). --- electron-builder.json5 | 2 + src/components/video-editor/SettingsPanel.tsx | 30 ++--- .../video-editor/projectPersistence.test.ts | 40 ++++++ .../video-editor/projectPersistence.ts | 16 ++- src/lib/wallpaper.test.ts | 56 ++++++-- src/lib/wallpaper.ts | 12 +- src/vite-env.d.ts | 124 ------------------ 7 files changed, 120 insertions(+), 160 deletions(-) diff --git a/electron-builder.json5 b/electron-builder.json5 index 441eda4..ca053ef 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -20,6 +20,8 @@ "!CONTRIBUTING.md", "!LICENSE" ], + // Asset layout contract: "wallpapers/" under resourcesPath must align with + // assetBaseDir in electron/preload.ts (packaged branch). "extraResources": [ { "from": "public/wallpapers", diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 91840bc..78d6bb4 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -321,7 +321,10 @@ export function SettingsPanel({ onWebcamSizePresetCommit, }: SettingsPanelProps) { const t = useScopedT("settings"); - const wallpaperPaths = useMemo(() => WALLPAPER_PATHS.map(resolveImageWallpaperUrl), []); + // Resolved URLs are for DOM rendering only (backgroundImage). The canonical + // `/wallpapers/wallpaperN.jpg` form in WALLPAPER_PATHS is what gets persisted + // on click — never the machine-specific file:// URL. + const wallpaperPreviewUrls = useMemo(() => WALLPAPER_PATHS.map(resolveImageWallpaperUrl), []); const [customImages, setCustomImages] = useState([]); const fileInputRef = useRef(null); const colorPalette = [ @@ -506,7 +509,7 @@ export function SettingsPanel({ setCustomImages((prev) => prev.filter((img) => img !== imageUrl)); // If the removed image was selected, clear selection if (selected === imageUrl) { - onWallpaperChange(wallpaperPaths[0] || WALLPAPER_PATHS[0]); + onWallpaperChange(WALLPAPER_PATHS[0]); } }; @@ -1126,23 +1129,12 @@ export function SettingsPanel({ ); })} - {wallpaperPaths.map((path) => { - const isSelected = (() => { - if (!selected) return false; - if (selected === path) return true; - try { - const clean = (s: string) => - s.replace(/^file:\/\//, "").replace(/^\//, ""); - if (clean(selected).endsWith(clean(path))) return true; - if (clean(path).endsWith(clean(selected))) return true; - } catch { - // Best-effort comparison; fallback to strict match. - } - return false; - })(); + {WALLPAPER_PATHS.map((canonicalPath, i) => { + const previewUrl = wallpaperPreviewUrls[i] ?? canonicalPath; + const isSelected = selected === canonicalPath; return (
onWallpaperChange(path)} + onClick={() => onWallpaperChange(canonicalPath)} role="button" /> ); diff --git a/src/components/video-editor/projectPersistence.test.ts b/src/components/video-editor/projectPersistence.test.ts index 14dc240..d816f48 100644 --- a/src/components/video-editor/projectPersistence.test.ts +++ b/src/components/video-editor/projectPersistence.test.ts @@ -197,3 +197,43 @@ it("detects unsaved changes from differing snapshots", () => { expect(hasProjectUnsavedChanges("same", "same")).toBe(false); expect(hasProjectUnsavedChanges("current", "baseline")).toBe(true); }); + +describe("wallpaper legacy normalization", () => { + it("rewrites resolved file:// resources paths from pre-fix projects", () => { + const normalized = normalizeProjectEditor({ + wallpaper: "file:///opt/Openscreen/resources/assets/wallpapers/wallpaper5.jpg", + }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper5.jpg"); + }); + + it("rewrites resolved file:// paths under the new resources/wallpapers layout", () => { + const normalized = normalizeProjectEditor({ + wallpaper: "file:///opt/Openscreen/resources/wallpapers/wallpaper3.jpg", + }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper3.jpg"); + }); + + it("rewrites unpackaged dev paths (public/wallpapers/…)", () => { + const normalized = normalizeProjectEditor({ + wallpaper: "file:///home/user/project/public/wallpapers/wallpaper1.jpg", + }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper1.jpg"); + }); + + it("leaves canonical relative paths untouched", () => { + const normalized = normalizeProjectEditor({ wallpaper: "/wallpapers/wallpaper2.jpg" }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper2.jpg"); + }); + + it("leaves data URIs untouched", () => { + const dataUri = "data:image/png;base64,AAA"; + expect(normalizeProjectEditor({ wallpaper: dataUri }).wallpaper).toBe(dataUri); + }); + + it("leaves colors and gradients untouched", () => { + expect(normalizeProjectEditor({ wallpaper: "#1a1a2e" }).wallpaper).toBe("#1a1a2e"); + expect( + normalizeProjectEditor({ wallpaper: "linear-gradient(90deg, red, blue)" }).wallpaper, + ).toBe("linear-gradient(90deg, red, blue)"); + }); +}); diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 6d8f689..c0def97 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -40,6 +40,17 @@ import { const VALID_BLUR_SHAPES = new Set(["rectangle", "oval", "freehand"] as const); +// Pre-fix projects could persist resolved file:// URLs (machine-specific) instead +// of the canonical `/wallpapers/wallpaperN.jpg` form. Rewrite those on load so +// they resolve against the current install's resources directory. +const LEGACY_FILE_WALLPAPER_RE = /^file:\/\/.*?\/(?:assets\/)?wallpapers\/(wallpaper\d+\.jpg)$/i; + +function normalizeWallpaperValue(value: string): string { + const match = LEGACY_FILE_WALLPAPER_RE.exec(value); + if (!match) return value; + return `/wallpapers/${match[1]}`; +} + export { WALLPAPER_PATHS }; export const PROJECT_VERSION = 2; @@ -422,7 +433,10 @@ export function normalizeProjectEditor(editor: Partial): Pro const cropHeight = clamp(rawCropHeight, 0.01, 1 - cropY); return { - wallpaper: typeof editor.wallpaper === "string" ? editor.wallpaper : DEFAULT_WALLPAPER, + wallpaper: + typeof editor.wallpaper === "string" + ? normalizeWallpaperValue(editor.wallpaper) + : DEFAULT_WALLPAPER, shadowIntensity: typeof editor.shadowIntensity === "number" ? editor.shadowIntensity : 0, showBlur: typeof editor.showBlur === "boolean" ? editor.showBlur : false, motionBlurAmount: isFiniteNumber(editor.motionBlurAmount) diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts index f1cb80b..6e1b74a 100644 --- a/src/lib/wallpaper.test.ts +++ b/src/lib/wallpaper.test.ts @@ -64,6 +64,16 @@ describe("classifyWallpaper", () => { expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); }); + it("repeating-linear gradient", () => { + const v = "repeating-linear-gradient(45deg, red 0 10px, blue 10px 20px)"; + expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); + }); + + it("repeating-radial gradient", () => { + const v = "repeating-radial-gradient(circle, red, blue 20px)"; + expect(classifyWallpaper(v)).toEqual({ kind: "gradient", value: v }); + }); + it("leading-slash image path", () => { expect(classifyWallpaper("/wallpapers/wallpaper1.jpg")).toEqual({ kind: "image", @@ -164,16 +174,24 @@ describe("resolveImageWallpaperUrl", () => { expect(() => resolveImageWallpaperUrl("/etc/passwd")).toThrow(BackgroundLoadError); }); - it("rejects traversal attempts", () => { - expect(() => resolveImageWallpaperUrl("/wallpapers/../etc/passwd")).toThrow( - UnsafeAssetPathError, - ); + it("wraps traversal attempts in BackgroundLoadError (preserves UnsafeAssetPathError as cause)", () => { + try { + resolveImageWallpaperUrl("/wallpapers/../etc/passwd"); + expect.fail("should have thrown"); + } catch (err) { + expect(err).toBeInstanceOf(BackgroundLoadError); + expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + } }); - it("rejects percent-encoded traversal", () => { - expect(() => resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar")).toThrow( - UnsafeAssetPathError, - ); + it("wraps percent-encoded traversal in BackgroundLoadError", () => { + try { + resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar"); + expect.fail("should have thrown"); + } catch (err) { + expect(err).toBeInstanceOf(BackgroundLoadError); + expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + } }); it("resolves via electronAPI.assetBaseUrl when not http", () => { @@ -198,15 +216,19 @@ describe("resolveImageWallpaperUrl", () => { ); }); - it("throws loudly when assetBaseUrl is empty (no silent fallback)", () => { + it("wraps AssetBaseUnavailableError in BackgroundLoadError when assetBaseUrl is empty", () => { vi.stubGlobal("window", { ...globalThis.window, location: { protocol: "file:" }, electronAPI: { assetBaseUrl: "" }, }); - expect(() => resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg")).toThrow( - AssetBaseUnavailableError, - ); + try { + resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg"); + expect.fail("should have thrown"); + } catch (err) { + expect(err).toBeInstanceOf(BackgroundLoadError); + expect((err as BackgroundLoadError).cause).toBeInstanceOf(AssetBaseUnavailableError); + } }); }); @@ -229,6 +251,16 @@ describe("BackgroundLoadError", () => { expect(err.displayUrl).toBe("data:…"); }); + it("displayUrl returns sentinel for empty-basename URLs", () => { + const err = new BackgroundLoadError("file:///"); + expect(err.displayUrl).toBe("(unknown)"); + }); + + it("displayUrl returns sentinel for unparseable bare slash", () => { + const err = new BackgroundLoadError("/"); + expect(err.displayUrl).toBe("(unknown)"); + }); + it("preserves cause when provided", () => { const cause = new Error("inner"); const err = new BackgroundLoadError("file:///missing.jpg", cause); diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index c86ce43..34869d7 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -14,7 +14,7 @@ export type WallpaperClassification = | { kind: "gradient"; value: string } | { kind: "image"; path: string }; -const GRADIENT_RE = /^(linear|radial|conic)-gradient\(/; +const GRADIENT_RE = /^(repeating-)?(linear|radial|conic)-gradient\(/; const COLOR_FUNC_RE = /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(/; const IMAGE_URL_RE = /^(\/|https?:\/\/|file:\/\/|data:)/; @@ -53,7 +53,11 @@ export function resolveImageWallpaperUrl(imagePath: string): string { new Error(`Image wallpaper path must live under ${ALLOWED_IMAGE_PREFIX}`), ); } - return getAssetPath(withLeadingSlash.slice(1)); + try { + return getAssetPath(withLeadingSlash.slice(1)); + } catch (cause) { + throw new BackgroundLoadError(imagePath, cause); + } } export class BackgroundLoadError extends Error { @@ -79,9 +83,9 @@ function displayBasename(url: string): string { try { const parsed = new URL(url); const last = parsed.pathname.split("/").filter(Boolean).pop(); - return last ? decodeURIComponent(last) : url; + return last ? decodeURIComponent(last) : "(unknown)"; } catch { const last = url.split("/").filter(Boolean).pop(); - return last ?? url; + return last ?? "(unknown)"; } } diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index bdcb537..b7a0735 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,126 +1,2 @@ /// /// - -interface ProcessedDesktopSource { - id: string; - name: string; - display_id: string; - thumbnail: string | null; - appIcon: string | null; -} - -interface CursorTelemetryPoint { - timeMs: number; - cx: number; - cy: number; -} - -interface Window { - electronAPI: { - getSources: (opts: Electron.SourcesOptions) => Promise; - switchToEditor: () => Promise; - switchToHud: () => Promise; - startNewRecording: () => Promise<{ success: boolean; error?: string }>; - openSourceSelector: () => Promise; - selectSource: (source: ProcessedDesktopSource) => Promise; - getSelectedSource: () => Promise; - requestCameraAccess: () => Promise<{ - success: boolean; - granted: boolean; - status: string; - error?: string; - }>; - storeRecordedVideo: ( - videoData: ArrayBuffer, - fileName: string, - ) => Promise<{ - success: boolean; - path?: string; - session?: import("./lib/recordingSession").RecordingSession; - message?: string; - error?: string; - }>; - storeRecordedSession: ( - payload: import("./lib/recordingSession").StoreRecordedSessionInput, - ) => Promise<{ - success: boolean; - path?: string; - session?: import("./lib/recordingSession").RecordingSession; - message?: string; - error?: string; - }>; - getRecordedVideoPath: () => Promise<{ - success: boolean; - path?: string; - message?: string; - error?: string; - }>; - assetBaseUrl: string; - setRecordingState: (recording: boolean) => Promise; - getCursorTelemetry: (videoPath?: string) => Promise<{ - success: boolean; - samples: CursorTelemetryPoint[]; - message?: string; - error?: string; - }>; - onStopRecordingFromTray: (callback: () => void) => () => void; - openExternalUrl: (url: string) => Promise<{ success: boolean; error?: string }>; - saveExportedVideo: ( - videoData: ArrayBuffer, - fileName: string, - ) => Promise<{ - success: boolean; - path?: string; - message?: string; - canceled?: boolean; - }>; - openVideoFilePicker: () => Promise<{ success: boolean; path?: string; canceled?: boolean }>; - setCurrentVideoPath: (path: string) => Promise<{ success: boolean }>; - setCurrentRecordingSession: ( - session: import("./lib/recordingSession").RecordingSession | null, - ) => Promise<{ - success: boolean; - session?: import("./lib/recordingSession").RecordingSession; - }>; - getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>; - getCurrentRecordingSession: () => Promise<{ - success: boolean; - session?: import("./lib/recordingSession").RecordingSession; - }>; - clearCurrentVideoPath: () => Promise<{ success: boolean }>; - saveProjectFile: ( - projectData: unknown, - suggestedName?: string, - existingProjectPath?: string, - ) => Promise<{ - success: boolean; - path?: string; - message?: string; - canceled?: boolean; - error?: string; - }>; - loadProjectFile: () => Promise<{ - success: boolean; - path?: string; - project?: unknown; - message?: string; - canceled?: boolean; - error?: string; - }>; - loadCurrentProjectFile: () => Promise<{ - success: boolean; - path?: string; - project?: unknown; - message?: string; - canceled?: boolean; - error?: string; - }>; - onMenuLoadProject: (callback: () => void) => () => void; - onMenuSaveProject: (callback: () => void) => () => void; - onMenuSaveProjectAs: (callback: () => void) => () => void; - setMicrophoneExpanded: (expanded: boolean) => void; - setHasUnsavedChanges: (hasChanges: boolean) => void; - onRequestSaveBeforeClose: (callback: () => Promise | boolean) => () => void; - setLocale: (locale: string) => Promise; - }; -} From af159e8a2b60d4f4043f6556a4ac5cbba299fd66 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 18:58:34 -0500 Subject: [PATCH 138/152] tighten legacy normalizer and guard against BackgroundLoadError double-wrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer audit found two real risks in the prior amendment: 1. LEGACY_FILE_WALLPAPER_RE was too permissive. Any file:// URL containing /wallpapers/wallpaperN.jpg would match — including a user's own file at /home/me/wallpapers/wallpaper1.jpg that happened to share the name pattern. Silent data-loss potential: user's photo replaced with a bundled asset. In-app upload flow uses data: URIs today so it can't actually produce such a value, but the regex should be tight on intent. Now requires a known install-layout segment: resources/[assets/]wallpapers/ (packaged) or public/wallpapers/ (dev). 2. No upper bound on \d+. A corrupted or future-schema project with wallpaper99.jpg was silently rewritten to /wallpapers/wallpaper99.jpg which 404s. Now validates against WALLPAPER_PATHS; out-of-set bundled-looking values fall back to DEFAULT_WALLPAPER. Also applied R2.2 defensive guard: resolveImageWallpaperUrl's catch block now checks instanceof BackgroundLoadError and rethrows unchanged instead of wrapping a second time. Current getAssetPath cannot throw BackgroundLoadError so this is a future-proof against refactors. Tests: 56 pass (up from 54). Added coverage for "user file outside install dir stays untouched" and "bundled-looking but out-of-set falls back to default". --- .../video-editor/projectPersistence.test.ts | 18 +++++++++++++++--- .../video-editor/projectPersistence.ts | 15 ++++++++++----- src/lib/wallpaper.ts | 1 + 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/video-editor/projectPersistence.test.ts b/src/components/video-editor/projectPersistence.test.ts index d816f48..0659cb7 100644 --- a/src/components/video-editor/projectPersistence.test.ts +++ b/src/components/video-editor/projectPersistence.test.ts @@ -199,21 +199,21 @@ it("detects unsaved changes from differing snapshots", () => { }); describe("wallpaper legacy normalization", () => { - it("rewrites resolved file:// resources paths from pre-fix projects", () => { + it("rewrites pre-fix packaged paths (resources/assets/wallpapers/…)", () => { const normalized = normalizeProjectEditor({ wallpaper: "file:///opt/Openscreen/resources/assets/wallpapers/wallpaper5.jpg", }); expect(normalized.wallpaper).toBe("/wallpapers/wallpaper5.jpg"); }); - it("rewrites resolved file:// paths under the new resources/wallpapers layout", () => { + it("rewrites new packaged layout (resources/wallpapers/…)", () => { const normalized = normalizeProjectEditor({ wallpaper: "file:///opt/Openscreen/resources/wallpapers/wallpaper3.jpg", }); expect(normalized.wallpaper).toBe("/wallpapers/wallpaper3.jpg"); }); - it("rewrites unpackaged dev paths (public/wallpapers/…)", () => { + it("rewrites unpackaged dev layout (public/wallpapers/…)", () => { const normalized = normalizeProjectEditor({ wallpaper: "file:///home/user/project/public/wallpapers/wallpaper1.jpg", }); @@ -236,4 +236,16 @@ describe("wallpaper legacy normalization", () => { normalizeProjectEditor({ wallpaper: "linear-gradient(90deg, red, blue)" }).wallpaper, ).toBe("linear-gradient(90deg, red, blue)"); }); + + it("does NOT rewrite user files outside the known install layout", () => { + const userPath = "file:///home/user/Pictures/wallpapers/wallpaper1.jpg"; + expect(normalizeProjectEditor({ wallpaper: userPath }).wallpaper).toBe(userPath); + }); + + it("falls back to default for bundled paths outside WALLPAPER_PATHS", () => { + const normalized = normalizeProjectEditor({ + wallpaper: "file:///opt/Openscreen/resources/wallpapers/wallpaper99.jpg", + }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper1.jpg"); + }); }); diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index c0def97..7c963d7 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -40,15 +40,20 @@ import { const VALID_BLUR_SHAPES = new Set(["rectangle", "oval", "freehand"] as const); -// Pre-fix projects could persist resolved file:// URLs (machine-specific) instead -// of the canonical `/wallpapers/wallpaperN.jpg` form. Rewrite those on load so -// they resolve against the current install's resources directory. -const LEGACY_FILE_WALLPAPER_RE = /^file:\/\/.*?\/(?:assets\/)?wallpapers\/(wallpaper\d+\.jpg)$/i; +// Pre-fix projects could persist resolved file:// URLs (machine-specific) for +// bundled wallpapers. Rewrite only paths that match a known install layout +// (resources/[assets/]wallpapers for packaged, public/wallpapers for dev) so +// a legitimate user file that happens to live in a folder named "wallpapers" +// elsewhere is never silently replaced. +const LEGACY_FILE_WALLPAPER_RE = + /^file:\/\/.*?\/(?:resources\/(?:assets\/)?|public\/)wallpapers\/(wallpaper\d+\.jpg)$/i; +const CANONICAL_WALLPAPERS = new Set(WALLPAPER_PATHS); function normalizeWallpaperValue(value: string): string { const match = LEGACY_FILE_WALLPAPER_RE.exec(value); if (!match) return value; - return `/wallpapers/${match[1]}`; + const canonical = `/wallpapers/${match[1]}`; + return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER; } export { WALLPAPER_PATHS }; diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index 34869d7..7361df2 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -56,6 +56,7 @@ export function resolveImageWallpaperUrl(imagePath: string): string { try { return getAssetPath(withLeadingSlash.slice(1)); } catch (cause) { + if (cause instanceof BackgroundLoadError) throw cause; throw new BackgroundLoadError(imagePath, cause); } } From 373319808e99dc9e2bf9c226691aeebba851dbb2 Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 21:58:59 -0500 Subject: [PATCH 139/152] cover Windows drive-letter file URLs in legacy wallpaper normalizer test --- src/components/video-editor/projectPersistence.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/video-editor/projectPersistence.test.ts b/src/components/video-editor/projectPersistence.test.ts index 0659cb7..8a17b9e 100644 --- a/src/components/video-editor/projectPersistence.test.ts +++ b/src/components/video-editor/projectPersistence.test.ts @@ -220,6 +220,13 @@ describe("wallpaper legacy normalization", () => { expect(normalized.wallpaper).toBe("/wallpapers/wallpaper1.jpg"); }); + it("rewrites Windows-style file URLs with drive letter", () => { + const normalized = normalizeProjectEditor({ + wallpaper: "file:///C:/Users/me/openscreen/resources/wallpapers/wallpaper2.jpg", + }); + expect(normalized.wallpaper).toBe("/wallpapers/wallpaper2.jpg"); + }); + it("leaves canonical relative paths untouched", () => { const normalized = normalizeProjectEditor({ wallpaper: "/wallpapers/wallpaper2.jpg" }); expect(normalized.wallpaper).toBe("/wallpapers/wallpaper2.jpg"); From e06e40dbc2614f402d1034f66cae585c92d5681a Mon Sep 17 00:00:00 2001 From: Enriquefft Date: Fri, 24 Apr 2026 22:34:00 -0500 Subject: [PATCH 140/152] clean review nits: typed prefix sentinel, instanceof narrowing, drop dead re-export - Replace anonymous Error in resolveImageWallpaperUrl with typed UnsafeImagePrefixError, mirroring UnsafeAssetPathError so cause chains stay discriminable. - Replace `(err as BackgroundLoadError).cause` casts in wallpaper tests with instanceof narrowing (no `as` per project rules). - Remove unused `WALLPAPER_PATHS` re-export from projectPersistence; consumers import directly from @/lib/wallpaper (SSOT). --- .../video-editor/projectPersistence.ts | 2 -- src/lib/wallpaper.test.ts | 23 ++++++++++++------- src/lib/wallpaper.ts | 12 ++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 7c963d7..7259c1e 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -56,8 +56,6 @@ function normalizeWallpaperValue(value: string): string { return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER; } -export { WALLPAPER_PATHS }; - export const PROJECT_VERSION = 2; export interface ProjectEditorState { diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts index 6e1b74a..02596aa 100644 --- a/src/lib/wallpaper.test.ts +++ b/src/lib/wallpaper.test.ts @@ -5,6 +5,7 @@ import { classifyWallpaper, DEFAULT_WALLPAPER, resolveImageWallpaperUrl, + UnsafeImagePrefixError, WALLPAPER_COUNT, WALLPAPER_PATHS, } from "./wallpaper"; @@ -170,8 +171,14 @@ describe("resolveImageWallpaperUrl", () => { expect(resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe("/wallpapers/my%20image.jpg"); }); - it("rejects image paths outside /wallpapers/", () => { - expect(() => resolveImageWallpaperUrl("/etc/passwd")).toThrow(BackgroundLoadError); + it("rejects image paths outside /wallpapers/ with UnsafeImagePrefixError as cause", () => { + try { + resolveImageWallpaperUrl("/etc/passwd"); + expect.fail("should have thrown"); + } catch (err) { + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeImagePrefixError); + } }); it("wraps traversal attempts in BackgroundLoadError (preserves UnsafeAssetPathError as cause)", () => { @@ -179,8 +186,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/../etc/passwd"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeAssetPathError); } }); @@ -189,8 +196,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeAssetPathError); } }); @@ -226,8 +233,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(AssetBaseUnavailableError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(AssetBaseUnavailableError); } }); }); diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index 7361df2..6974a04 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -37,6 +37,13 @@ export function classifyWallpaper(value: string): WallpaperClassification { const ALLOWED_IMAGE_PREFIX = "/wallpapers/"; +export class UnsafeImagePrefixError extends Error { + constructor(prefix: string) { + super(`Image wallpaper path must live under ${prefix}`); + this.name = "UnsafeImagePrefixError"; + } +} + export function resolveImageWallpaperUrl(imagePath: string): string { if ( imagePath.startsWith("http://") || @@ -48,10 +55,7 @@ export function resolveImageWallpaperUrl(imagePath: string): string { } const withLeadingSlash = imagePath.startsWith("/") ? imagePath : `/${imagePath}`; if (!withLeadingSlash.startsWith(ALLOWED_IMAGE_PREFIX)) { - throw new BackgroundLoadError( - imagePath, - new Error(`Image wallpaper path must live under ${ALLOWED_IMAGE_PREFIX}`), - ); + throw new BackgroundLoadError(imagePath, new UnsafeImagePrefixError(ALLOWED_IMAGE_PREFIX)); } try { return getAssetPath(withLeadingSlash.slice(1)); From 657d55bd7265da4cbbd0f97773b4cc09373f9367 Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Sat, 25 Apr 2026 15:08:01 +0530 Subject: [PATCH 141/152] fix: rethrow play error so allowPlaybackRef resets on failure --- src/components/video-editor/VideoPlayback.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index e48dc3a..1dda826 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -369,7 +369,10 @@ const VideoPlayback = forwardRef( if (!vid) return; try { allowPlaybackRef.current = true; - await vid.play().catch((err) => console.log("PLAY ERROR:", err)); + await vid.play().catch((err) => { + console.log("PLAY ERROR:", err); + throw err; + }); } catch (error) { allowPlaybackRef.current = false; throw error; From e1c67c4e92611ab3ba82d5fbde248734699c117a Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 25 Apr 2026 16:44:57 -0700 Subject: [PATCH 142/152] Revert "Merge pull request #373 from Moncef-Mhz/adjust-zoom-speed" This reverts commit a6ae0e6d9889530ea1f1f1674b5312652153b7be, reversing changes made to db10f92c49d230387f8afcb7f64225fe94c8c5bf. --- src/components/video-editor/SettingsPanel.tsx | 48 +------- src/components/video-editor/VideoEditor.tsx | 34 ----- src/components/video-editor/timeline/Item.tsx | 116 +----------------- .../video-editor/timeline/TimelineEditor.tsx | 12 -- src/components/video-editor/types.ts | 2 - .../videoPlayback/zoomRegionUtils.ts | 44 ++----- src/i18n/locales/en/settings.json | 7 -- src/i18n/locales/es/settings.json | 7 -- src/i18n/locales/zh-CN/settings.json | 7 -- 9 files changed, 13 insertions(+), 264 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 78d6bb4..f21f018 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -221,9 +221,6 @@ interface SettingsPanelProps { onWebcamLayoutPresetChange?: (preset: WebcamLayoutPreset) => void; webcamMaskShape?: import("./types").WebcamMaskShape; onWebcamMaskShapeChange?: (shape: import("./types").WebcamMaskShape) => void; - selectedZoomInDuration?: number; - selectedZoomOutDuration?: number; - onZoomDurationChange?: (zoomIn: number, zoomOut: number) => void; webcamSizePreset?: WebcamSizePreset; onWebcamSizePresetChange?: (size: WebcamSizePreset) => void; onWebcamSizePresetCommit?: () => void; @@ -240,13 +237,6 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [ { depth: 6, label: "5×" }, ]; -const ZOOM_SPEED_OPTIONS = [ - { label: "Instant", zoomIn: 0, zoomOut: 0 }, - { label: "Fast", zoomIn: 500, zoomOut: 350 }, - { label: "Smooth", zoomIn: 1522, zoomOut: 1015 }, - { label: "Lazy", zoomIn: 3000, zoomOut: 2000 }, -]; - export function SettingsPanel({ selected, onWallpaperChange, @@ -313,9 +303,6 @@ export function SettingsPanel({ onWebcamLayoutPresetChange, webcamMaskShape = "rectangle", onWebcamMaskShapeChange, - selectedZoomInDuration, - selectedZoomOutDuration, - onZoomDurationChange, webcamSizePreset = DEFAULT_WEBCAM_SIZE_PRESET, onWebcamSizePresetChange, onWebcamSizePresetCommit, @@ -649,39 +636,6 @@ export function SettingsPanel({ )}
)} - - {zoomEnabled && ( -
- - {t("zoom.speed.title") || "Zoom Speed"} - -
- {ZOOM_SPEED_OPTIONS.map((opt) => { - const isActive = - selectedZoomInDuration !== undefined && - selectedZoomOutDuration !== undefined && - Math.round(selectedZoomInDuration) === Math.round(opt.zoomIn) && - Math.round(selectedZoomOutDuration) === Math.round(opt.zoomOut); - return ( - - ); - })} -
-
- )} {zoomEnabled && (
diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index aeca711..8fa210b 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -1,14 +1,9 @@ import type { Span } from "dnd-timeline"; -import { useItem, useTimelineContext } from "dnd-timeline"; +import { useItem } from "dnd-timeline"; import { Gauge, MessageSquare, Scissors, ZoomIn } from "lucide-react"; import { useMemo } from "react"; import { useScopedT } from "@/contexts/I18nContext"; import { cn } from "@/lib/utils"; -import { - DEFAULT_ZOOM_IN_MS, - DEFAULT_ZOOM_OUT_MS, - getDurations, -} from "../videoPlayback/zoomRegionUtils"; import glassStyles from "./ItemGlass.module.css"; interface ItemProps { @@ -19,10 +14,7 @@ interface ItemProps { isSelected?: boolean; onSelect?: () => void; zoomDepth?: number; - zoomInDurationMs?: number; - zoomOutDurationMs?: number; speedValue?: number; - onZoomDurationChange?: (id: string, zoomIn: number, zoomOut: number) => void; variant?: "zoom" | "trim" | "annotation" | "speed" | "blur"; } @@ -53,15 +45,11 @@ export default function Item({ isSelected = false, onSelect, zoomDepth = 1, - zoomInDurationMs, - zoomOutDurationMs, speedValue, variant = "zoom", children, - onZoomDurationChange, }: ItemProps) { const t = useScopedT("timeline"); - const { pixelsToValue } = useTimelineContext(); const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, span, @@ -93,16 +81,6 @@ export default function Item({ const MIN_ITEM_PX = 6; const safeItemStyle = { ...itemStyle, minWidth: MIN_ITEM_PX }; - const { zoomIn, zoomOut } = useMemo(() => { - if (!isZoom) return { zoomIn: 0, zoomOut: 0 }; - return getDurations({ - startMs: span.start, - endMs: span.end, - zoomInDurationMs, - zoomOutDurationMs, - }); - }, [isZoom, span.start, span.end, zoomInDurationMs, zoomOutDurationMs]); - return (
- {isZoom && ( - <> - {/* Transition In Marker */} -
- {/* Draggable handle for Transition In */} -
{ - e.stopPropagation(); - e.preventDefault(); - const target = e.currentTarget; - target.setPointerCapture(e.pointerId); - - const startX = e.clientX; - const initialZoomIn = zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; - const initialZoomOut = zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; - - const onPointerMove = (moveEvent: PointerEvent) => { - const deltaPx = moveEvent.clientX - startX; - const deltaMs = pixelsToValue(deltaPx); - const newDuration = Math.max( - 0, - Math.min(initialZoomIn + deltaMs, span.end - span.start - initialZoomOut), - ); - onZoomDurationChange?.(id, newDuration, initialZoomOut); - }; - - const onPointerUp = () => { - target.releasePointerCapture(e.pointerId); - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", onPointerUp); - }; - - window.addEventListener("pointermove", onPointerMove); - window.addEventListener("pointerup", onPointerUp); - }} - /> - {/* Transition Out Marker */} -
- {/* Draggable handle for Transition Out */} -
{ - e.stopPropagation(); - e.preventDefault(); - const target = e.currentTarget; - target.setPointerCapture(e.pointerId); - - const startX = e.clientX; - const initialZoomIn = zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; - const initialZoomOut = zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; - - const onPointerMove = (moveEvent: PointerEvent) => { - const deltaPx = startX - moveEvent.clientX; // Inverted because right-anchored - const deltaMs = pixelsToValue(deltaPx); - const newDuration = Math.max( - 0, - Math.min(initialZoomOut + deltaMs, span.end - span.start - initialZoomIn), - ); - onZoomDurationChange?.(id, initialZoomIn, newDuration); - }; - - const onPointerUp = () => { - target.releasePointerCapture(e.pointerId); - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", onPointerUp); - }; - - window.addEventListener("pointermove", onPointerMove); - window.addEventListener("pointerup", onPointerUp); - }} - /> - - )}
void; onZoomSuggested?: (span: Span, focus: ZoomFocus) => void; onZoomSpanChange: (id: string, span: Span) => void; - onZoomDurationChange: (id: string, zoomIn: number, zoomOut: number) => void; onZoomDelete: (id: string) => void; selectedZoomId: string | null; onSelectZoom: (id: string | null) => void; @@ -104,8 +103,6 @@ interface TimelineRenderItem { label: string; zoomDepth?: number; speedValue?: number; - zoomInDurationMs?: number; - zoomOutDurationMs?: number; variant: "zoom" | "trim" | "annotation" | "speed" | "blur"; } @@ -542,7 +539,6 @@ function Timeline({ selectedAnnotationId, selectedBlurId, selectedSpeedId, - onZoomDurationChange, keyframes = [], }: { items: TimelineRenderItem[]; @@ -560,7 +556,6 @@ function Timeline({ selectedAnnotationId?: string | null; selectedBlurId?: string | null; selectedSpeedId?: string | null; - onZoomDurationChange: (id: string, zoomIn: number, zoomOut: number) => void; keyframes?: { id: string; time: number }[]; }) { const t = useScopedT("timeline"); @@ -687,9 +682,6 @@ function Timeline({ isSelected={item.id === selectedZoomId} onSelect={() => onSelectZoom?.(item.id)} zoomDepth={item.zoomDepth} - zoomInDurationMs={item.zoomInDurationMs} - zoomOutDurationMs={item.zoomOutDurationMs} - onZoomDurationChange={onZoomDurationChange} variant="zoom" > {item.label} @@ -778,7 +770,6 @@ export default function TimelineEditor({ onZoomAdded, onZoomSuggested, onZoomSpanChange, - onZoomDurationChange, onZoomDelete, selectedZoomId, onSelectZoom, @@ -1346,8 +1337,6 @@ export default function TimelineEditor({ span: { start: region.startMs, end: region.endMs }, label: t("labels.zoomItem", { index: String(index + 1) }), zoomDepth: region.depth, - zoomInDurationMs: region.zoomInDurationMs, - zoomOutDurationMs: region.zoomOutDurationMs, variant: "zoom", })); @@ -1604,7 +1593,6 @@ export default function TimelineEditor({ selectedAnnotationId={selectedAnnotationId} selectedBlurId={selectedBlurId} selectedSpeedId={selectedSpeedId} - onZoomDurationChange={onZoomDurationChange} keyframes={keyframes} /> diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index 87e4331..046f428 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -33,8 +33,6 @@ export interface ZoomRegion { depth: ZoomDepth; focus: ZoomFocus; focusMode?: ZoomFocusMode; - zoomInDurationMs?: number; - zoomOutDurationMs?: number; } export interface CursorTelemetryPoint { diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts index 88d08c1..e5c16e1 100644 --- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts +++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts @@ -7,6 +7,7 @@ import { clamp01, cubicBezier, easeOutScreenStudio } from "./mathUtils"; const CHAINED_ZOOM_PAN_GAP_MS = 1500; const CONNECTED_ZOOM_PAN_DURATION_MS = 1000; +const ZOOM_IN_OVERLAP_MS = 500; type DominantRegionOptions = { connectZooms?: boolean; @@ -37,49 +38,26 @@ function easeConnectedPan(value: number) { return cubicBezier(0.1, 0.0, 0.2, 1.0, value); } -export const DEFAULT_ZOOM_OUT_MS = TRANSITION_WINDOW_MS; -export const DEFAULT_ZOOM_IN_MS = ZOOM_IN_TRANSITION_WINDOW_MS; - -export function getDurations(region: { - startMs: number; - endMs: number; - zoomInDurationMs?: number; - zoomOutDurationMs?: number; -}) { - let zoomIn = region.zoomInDurationMs ?? DEFAULT_ZOOM_IN_MS; - let zoomOut = region.zoomOutDurationMs ?? DEFAULT_ZOOM_OUT_MS; - - const duration = region.endMs - region.startMs; - if (zoomIn + zoomOut > duration) { - const scale = duration / (zoomIn + zoomOut); - zoomIn *= scale; - zoomOut *= scale; - } - - return { zoomIn, zoomOut }; -} - export function computeRegionStrength(region: ZoomRegion, timeMs: number) { - const { zoomIn, zoomOut } = getDurations(region); + const zoomInEnd = region.startMs + ZOOM_IN_OVERLAP_MS; + const leadInStart = zoomInEnd - ZOOM_IN_TRANSITION_WINDOW_MS; + const leadOutEnd = region.endMs + TRANSITION_WINDOW_MS; - if (timeMs < region.startMs || timeMs > region.endMs) { + if (timeMs < leadInStart || timeMs > leadOutEnd) { return 0; } - // Zooming in - if (timeMs < region.startMs + zoomIn) { - const progress = Math.max(0, Math.min(1, (timeMs - region.startMs) / zoomIn)); + if (timeMs < zoomInEnd) { + const progress = (timeMs - leadInStart) / ZOOM_IN_TRANSITION_WINDOW_MS; return easeOutScreenStudio(progress); } - // Zooming out - if (timeMs > region.endMs - zoomOut) { - const progress = Math.max(0, Math.min(1, (region.endMs - timeMs) / zoomOut)); - return easeOutScreenStudio(progress); + if (timeMs <= region.endMs) { + return 1; } - // Full zoom - return 1; + const progress = clamp01((timeMs - region.endMs) / TRANSITION_WINDOW_MS); + return 1 - easeOutScreenStudio(progress); } function getLinearFocus(start: ZoomFocus, end: ZoomFocus, amount: number): ZoomFocus { diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 00e7c08..f737cfc 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -8,13 +8,6 @@ "manual": "Manual", "auto": "Auto", "autoDescription": "Camera follows the recorded cursor position" - }, - "speed": { - "title": "Zoom Speed", - "instant": "Instant", - "fast": "Fast", - "smooth": "Smooth", - "lazy": "Lazy" } }, "speed": { diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 92160bd..c48ed5c 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -8,13 +8,6 @@ "manual": "Manual", "auto": "Auto", "autoDescription": "La cámara sigue la posición del cursor grabado" - }, - "speed": { - "title": "Velocidad de zoom", - "instant": "Instantáneo", - "fast": "Rápido", - "smooth": "Suave", - "lazy": "Lento" } }, "speed": { diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index 10a8ecd..299483a 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -8,13 +8,6 @@ "manual": "手动", "auto": "自动", "autoDescription": "摄像头跟随录制时的光标位置" - }, - "speed": { - "title": "缩放速度", - "instant": "即时", - "fast": "快速", - "smooth": "平滑", - "lazy": "缓慢" } }, "speed": { From 8458cbb40e95858e10ec746d6e358055262cc99c Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 25 Apr 2026 16:47:20 -0700 Subject: [PATCH 143/152] fix: pass asset base URL to preload via additionalArguments Sandboxed preloads (Electron's default with contextIsolation) cannot require node modules. Commit 702b733 added node:path / node:url imports to preload.ts which fail at load time: Unable to load preload script: dist-electron/preload.mjs Error: module not found: node:path This left window.electronAPI undefined, breaking every IPC call. Compute the asset base URL in main process (windows.ts) and pass it to preload via webPreferences.additionalArguments. Preload reads it from process.argv. Sync API for renderer is preserved. --- electron/preload.ts | 18 ++++++------------ electron/windows.ts | 13 ++++++++++++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/electron/preload.ts b/electron/preload.ts index ec5181c..12167e5 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -1,18 +1,12 @@ -import path from "node:path"; -import { pathToFileURL } from "node:url"; import { contextBridge, ipcRenderer } from "electron"; import type { RecordingSession, StoreRecordedSessionInput } from "../src/lib/recordingSession"; -// Asset base URL is a build-time constant per process; resolve once here so -// the renderer can consume it synchronously. Packaged: electron-builder -// extraResources copies public/wallpapers -> resources/wallpapers (see -// electron-builder.json5). Unpackaged: wallpapers live at /public/, -// and __dirname in dist-electron resolves to /dist-electron/. -const isPackagedProcess = !process.defaultApp; -const assetBaseDir = isPackagedProcess - ? process.resourcesPath - : path.join(__dirname, "..", "public"); -const assetBaseUrl = pathToFileURL(`${assetBaseDir}${path.sep}`).toString(); +// Asset base URL is passed from the main process via webPreferences.additionalArguments +// (see windows.ts). Sandboxed preloads cannot import node:path / node:url, so we +// can't compute it here. +const ASSET_BASE_URL_ARG_PREFIX = "--asset-base-url="; +const assetBaseUrlArg = process.argv.find((arg) => arg.startsWith(ASSET_BASE_URL_ARG_PREFIX)); +const assetBaseUrl = assetBaseUrlArg ? assetBaseUrlArg.slice(ASSET_BASE_URL_ARG_PREFIX.length) : ""; contextBridge.exposeInMainWorld("electronAPI", { assetBaseUrl, diff --git a/electron/windows.ts b/electron/windows.ts index daa6e5e..f94009a 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { BrowserWindow, ipcMain, screen } from "electron"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -9,6 +9,13 @@ const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; const RENDERER_DIST = path.join(APP_ROOT, "dist"); const HEADLESS = process.env["HEADLESS"] === "true"; +// Asset base URL for renderer (wallpapers, etc.). Packaged: extraResources copies +// public/wallpapers -> resources/wallpapers. Unpackaged: /public/. +const ASSET_BASE_DIR = process.defaultApp + ? path.join(__dirname, "..", "public") + : process.resourcesPath; +const ASSET_BASE_URL_ARG = `--asset-base-url=${pathToFileURL(`${ASSET_BASE_DIR}${path.sep}`).toString()}`; + let hudOverlayWindow: BrowserWindow | null = null; ipcMain.on("hud-overlay-hide", () => { @@ -50,6 +57,7 @@ export function createHudOverlayWindow(): BrowserWindow { show: !HEADLESS, webPreferences: { preload: path.join(__dirname, "preload.mjs"), + additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, backgroundThrottling: false, @@ -110,6 +118,7 @@ export function createEditorWindow(): BrowserWindow { show: !HEADLESS, webPreferences: { preload: path.join(__dirname, "preload.mjs"), + additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, webSecurity: false, @@ -156,6 +165,7 @@ export function createSourceSelectorWindow(): BrowserWindow { backgroundColor: "#00000000", webPreferences: { preload: path.join(__dirname, "preload.mjs"), + additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, }, @@ -207,6 +217,7 @@ export function createCountdownOverlayWindow(): BrowserWindow { show: false, webPreferences: { preload: path.join(__dirname, "preload.mjs"), + additionalArguments: [ASSET_BASE_URL_ARG], nodeIntegration: false, contextIsolation: true, backgroundThrottling: false, From 5e994d214edf8d88667a233eccaa5d86025add2e Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sun, 26 Apr 2026 17:17:49 -0700 Subject: [PATCH 144/152] fix perf playback choppiness --- src/components/video-editor/VideoPlayback.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 69363c5..35e0077 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1290,15 +1290,18 @@ const VideoPlayback = forwardRef( region: blurRegion, })), ].sort((a, b) => a.region.zIndex - b.region.zIndex); - const previewSnapshotCanvas = (() => { - const app = appRef.current; - if (!app?.renderer?.extract) return null; - try { - return app.renderer.extract.canvas(app.stage); - } catch { - return null; - } - })(); + const previewSnapshotCanvas = + filteredBlurRegions.length > 0 + ? (() => { + const app = appRef.current; + if (!app?.renderer?.extract) return null; + try { + return app.renderer.extract.canvas(app.stage); + } catch { + return null; + } + })() + : null; // Handle click-through cycling: when clicking same annotation, cycle to next const handleAnnotationClick = (clickedId: string) => { From 1fefde888198b030e7bd2d3b7ff1f80c58146aa5 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sun, 26 Apr 2026 17:25:20 -0700 Subject: [PATCH 145/152] auto zoom marker --- src/components/video-editor/timeline/Item.tsx | 10 +++++++++- .../video-editor/timeline/TimelineEditor.tsx | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index 8fa210b..d2b80c7 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -1,6 +1,6 @@ import type { Span } from "dnd-timeline"; import { useItem } from "dnd-timeline"; -import { Gauge, MessageSquare, Scissors, ZoomIn } from "lucide-react"; +import { Gauge, MessageSquare, MousePointer2, Scissors, ZoomIn } from "lucide-react"; import { useMemo } from "react"; import { useScopedT } from "@/contexts/I18nContext"; import { cn } from "@/lib/utils"; @@ -15,6 +15,7 @@ interface ItemProps { onSelect?: () => void; zoomDepth?: number; speedValue?: number; + isAutoFocus?: boolean; variant?: "zoom" | "trim" | "annotation" | "speed" | "blur"; } @@ -46,6 +47,7 @@ export default function Item({ onSelect, zoomDepth = 1, speedValue, + isAutoFocus = false, variant = "zoom", children, }: ItemProps) { @@ -134,6 +136,12 @@ export default function Item({ {ZOOM_LABELS[zoomDepth] || `${zoomDepth}×`} + {isAutoFocus && ( + + )} ) : isTrim ? ( <> diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index b1254b5..6fe3474 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -103,6 +103,7 @@ interface TimelineRenderItem { label: string; zoomDepth?: number; speedValue?: number; + isAutoFocus?: boolean; variant: "zoom" | "trim" | "annotation" | "speed" | "blur"; } @@ -682,6 +683,7 @@ function Timeline({ isSelected={item.id === selectedZoomId} onSelect={() => onSelectZoom?.(item.id)} zoomDepth={item.zoomDepth} + isAutoFocus={item.isAutoFocus} variant="zoom" > {item.label} @@ -1337,6 +1339,7 @@ export default function TimelineEditor({ span: { start: region.startMs, end: region.endMs }, label: t("labels.zoomItem", { index: String(index + 1) }), zoomDepth: region.depth, + isAutoFocus: region.focusMode === "auto", variant: "zoom", })); From 3b9b4192bf650fc55fd12dafffdccad1448904e8 Mon Sep 17 00:00:00 2001 From: shaun0927 <70629228+shaun0927@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:27:14 +0900 Subject: [PATCH 146/152] fix: key cursor telemetry batches by recordingId for safe discard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit discardLatestPending() popped whichever batch happened to be at the back of the queue. With a Stop → Record → Discard sequence, the pending queue can have recording B's batch sitting in front of A's by the time A's finalize callback resolves (because finalizeRecording awaits fixWebmDuration), so the discard targets the wrong recording. Tag each completed batch with the recording id supplied at startSession() time and replace discardLatestPending() with discardBatch(recordingId). takeNextBatch() now returns the full {recordingId, samples} shape so prependBatch() can re-queue it on write-failure without losing the id. The renderer already owns a stable recordingId (Date.now() in useScreenRecorder) and the IPC surface threads it through set-recording-state and discard-cursor-telemetry. Adds a regression test that mirrors FabLrc's scenario in PR #457: two recordings finalize, A is discarded after B has already been queued, and the buffer must drop A while keeping B intact. --- electron/electron-env.d.ts | 4 +- electron/ipc/handlers.ts | 25 +++-- electron/preload.ts | 8 +- src/hooks/useScreenRecorder.ts | 4 +- src/lib/cursorTelemetryBuffer.test.ts | 129 +++++++++++++++++--------- src/lib/cursorTelemetryBuffer.ts | 83 +++++++++++------ 6 files changed, 166 insertions(+), 87 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index ea364a1..08c06c6 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -63,8 +63,8 @@ interface Window { message?: string; error?: string; }>; - setRecordingState: (recording: boolean) => Promise; - discardCursorTelemetry: () => Promise; + setRecordingState: (recording: boolean, recordingId?: number) => Promise; + discardCursorTelemetry: (recordingId: number) => Promise; getCursorTelemetry: (videoPath?: string) => Promise<{ success: boolean; samples: CursorTelemetryPoint[]; diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index fc55006..7fe6c52 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -279,16 +279,20 @@ async function storeRecordedSessionFiles(payload: StoreRecordedSessionInput) { currentProjectPath = null; const telemetryPath = `${screenVideoPath}.cursor.json`; - const pendingSamples: CursorTelemetryPoint[] = cursorTelemetryBuffer.takeNextBatch(); - if (pendingSamples.length > 0) { + const pendingBatch = cursorTelemetryBuffer.takeNextBatch(); + if (pendingBatch && pendingBatch.samples.length > 0) { try { await fs.writeFile( telemetryPath, - JSON.stringify({ version: CURSOR_TELEMETRY_VERSION, samples: pendingSamples }, null, 2), + JSON.stringify( + { version: CURSOR_TELEMETRY_VERSION, samples: pendingBatch.samples }, + null, + 2, + ), "utf-8", ); } catch (err) { - cursorTelemetryBuffer.prependBatch(pendingSamples); + cursorTelemetryBuffer.prependBatch(pendingBatch); throw err; } } @@ -531,10 +535,15 @@ export function registerIpcHandlers( } }); - ipcMain.handle("set-recording-state", (_, recording: boolean) => { + ipcMain.handle("set-recording-state", (_, recording: boolean, recordingId?: number) => { if (recording) { stopCursorCapture(); - cursorTelemetryBuffer.startSession(); + // The renderer is the source of truth for the recording id (it + // uses the same id as the saved fileName). Fall back to a + // timestamp only if the renderer didn't supply one, so the + // buffer always has a stable key per session. + const id = typeof recordingId === "number" ? recordingId : Date.now(); + cursorTelemetryBuffer.startSession(id); cursorCaptureStartTimeMs = Date.now(); sampleCursorPoint(); cursorCaptureInterval = setInterval(sampleCursorPoint, CURSOR_SAMPLE_INTERVAL_MS); @@ -549,8 +558,8 @@ export function registerIpcHandlers( } }); - ipcMain.handle("discard-cursor-telemetry", () => { - cursorTelemetryBuffer.discardLatestPending(); + ipcMain.handle("discard-cursor-telemetry", (_, recordingId: number) => { + cursorTelemetryBuffer.discardBatch(recordingId); }); ipcMain.handle("get-cursor-telemetry", async (_, videoPath?: string) => { diff --git a/electron/preload.ts b/electron/preload.ts index 9367122..962e582 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -47,14 +47,14 @@ contextBridge.exposeInMainWorld("electronAPI", { getRecordedVideoPath: () => { return ipcRenderer.invoke("get-recorded-video-path"); }, - setRecordingState: (recording: boolean) => { - return ipcRenderer.invoke("set-recording-state", recording); + setRecordingState: (recording: boolean, recordingId?: number) => { + return ipcRenderer.invoke("set-recording-state", recording, recordingId); }, getCursorTelemetry: (videoPath?: string) => { return ipcRenderer.invoke("get-cursor-telemetry", videoPath); }, - discardCursorTelemetry: () => { - return ipcRenderer.invoke("discard-cursor-telemetry"); + discardCursorTelemetry: (recordingId: number) => { + return ipcRenderer.invoke("discard-cursor-telemetry", recordingId); }, onStopRecordingFromTray: (callback: () => void) => { const listener = () => callback(); diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index fd8a307..a95b672 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -225,7 +225,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { try { const screenBlob = await activeScreenRecorder.recordedBlobPromise; if (discardRecordingId.current === activeRecordingId) { - window.electronAPI?.discardCursorTelemetry(); + window.electronAPI?.discardCursorTelemetry(activeRecordingId); return; } if (screenBlob.size === 0) { @@ -554,7 +554,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { setRecording(true); setPaused(false); setElapsedSeconds(0); - window.electronAPI?.setRecordingState(true); + window.electronAPI?.setRecordingState(true, recordingId.current); const activeScreenRecorder = screenRecorder.current; const activeWebcamRecorder = webcamRecorder.current; diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts index 567a1eb..17174ac 100644 --- a/src/lib/cursorTelemetryBuffer.test.ts +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -2,141 +2,182 @@ import { describe, expect, it, vi } from "vitest"; import { type CursorTelemetryPoint, createCursorTelemetryBuffer } from "./cursorTelemetryBuffer"; function sample(tag: number): CursorTelemetryPoint { - return { timeMs: tag, cx: tag / 10, cy: tag / 10 }; + // Decouple the timestamp tag from the coordinate fixture so cursor + // points stay inside the normalized [0, 1] range that real samples use. + const normalized = (tag % 100) / 100; + return { timeMs: tag, cx: normalized, cy: normalized }; } describe("createCursorTelemetryBuffer", () => { it("stores samples captured during an active session", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); for (let i = 0; i < 3; i++) buf.push(sample(i)); buf.endSession(); const batch = buf.takeNextBatch(); - expect(batch).toHaveLength(3); - expect(batch[0]?.timeMs).toBe(0); + expect(batch?.recordingId).toBe(1); + expect(batch?.samples).toHaveLength(3); + expect(batch?.samples[0]?.timeMs).toBe(0); }); it("trims active samples past maxActiveSamples (ring behaviour)", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 2 }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); buf.push(sample(2)); buf.push(sample(3)); buf.endSession(); const batch = buf.takeNextBatch(); - expect(batch).toEqual([sample(2), sample(3)]); + expect(batch?.samples).toEqual([sample(2), sample(3)]); }); it("preserves earlier pending batches when a new session starts before store", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); // Recording 1 - buf.startSession(); + buf.startSession(1); buf.push(sample(101)); buf.push(sample(102)); buf.endSession(); // Recording 2 starts before recording 1's batch has been consumed - buf.startSession(); + buf.startSession(2); buf.push(sample(201)); buf.endSession(); const batch1 = buf.takeNextBatch(); const batch2 = buf.takeNextBatch(); - expect(batch1.map((s) => s.timeMs)).toEqual([101, 102]); - expect(batch2.map((s) => s.timeMs)).toEqual([201]); + expect(batch1?.recordingId).toBe(1); + expect(batch1?.samples.map((s) => s.timeMs)).toEqual([101, 102]); + expect(batch2?.recordingId).toBe(2); + expect(batch2?.samples.map((s) => s.timeMs)).toEqual([201]); }); - it("returns an empty batch when nothing is pending", () => { + it("returns null when nothing is pending", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - expect(buf.takeNextBatch()).toEqual([]); + expect(buf.takeNextBatch()).toBeNull(); }); it("drops empty sessions instead of queuing empty batches", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); buf.endSession(); expect(buf.pendingCount).toBe(0); - expect(buf.takeNextBatch()).toEqual([]); + expect(buf.takeNextBatch()).toBeNull(); }); it("caps the pending queue at maxPendingBatches to bound memory", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10, maxPendingBatches: 3 }); for (let round = 1; round <= 5; round++) { - buf.startSession(); + buf.startSession(round); buf.push(sample(round)); buf.endSession(); } expect(buf.pendingCount).toBe(3); // Oldest two batches (rounds 1 and 2) should have been dropped - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([3]); - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([4]); - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([5]); + expect(buf.takeNextBatch()?.recordingId).toBe(3); + expect(buf.takeNextBatch()?.recordingId).toBe(4); + expect(buf.takeNextBatch()?.recordingId).toBe(5); }); it("starting a new session clears in-progress samples but keeps pending batches", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); buf.endSession(); - buf.startSession(); + buf.startSession(2); buf.push(sample(99)); // Simulate another startSession before endSession (e.g. rapid restart) - buf.startSession(); + buf.startSession(3); expect(buf.activeCount).toBe(0); expect(buf.pendingCount).toBe(1); const batch = buf.takeNextBatch(); - expect(batch.map((s) => s.timeMs)).toEqual([1]); + expect(batch?.recordingId).toBe(1); + expect(batch?.samples.map((s) => s.timeMs)).toEqual([1]); }); - it("discardLatestPending() drops the most recently enqueued batch", () => { + it("discardBatch(id) drops only the batch produced by that recording id", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); buf.endSession(); - buf.startSession(); + buf.startSession(2); buf.push(sample(2)); buf.endSession(); expect(buf.pendingCount).toBe(2); - buf.discardLatestPending(); + expect(buf.discardBatch(1)).toBe(true); expect(buf.pendingCount).toBe(1); - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + expect(buf.takeNextBatch()?.recordingId).toBe(2); }); - it("discardLatestPending() is safe to call on an empty queue", () => { + it("discardBatch(id) targets the correct batch even when a later recording sits in front of it", () => { + // Regression test for the rapid Stop → Record → Discard sequence: + // recording A's finalize callback does async work (fixWebmDuration), + // recording B finishes in the meantime, then A's callback resolves + // with discard intent. The discard must drop A — not B, which + // happens to be the *latest* pending batch by the time discard runs. const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.discardLatestPending(); + + buf.startSession(1); + buf.push(sample(11)); + buf.endSession(); + + buf.startSession(2); + buf.push(sample(22)); + buf.endSession(); + + expect(buf.pendingCount).toBe(2); + expect(buf.discardBatch(1)).toBe(true); + + const remaining = buf.takeNextBatch(); + expect(remaining?.recordingId).toBe(2); + expect(remaining?.samples.map((s) => s.timeMs)).toEqual([22]); + expect(buf.takeNextBatch()).toBeNull(); + }); + + it("discardBatch(id) is a no-op (returns false) when the id is unknown or already drained", () => { + const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); + expect(buf.discardBatch(42)).toBe(false); + + buf.startSession(1); + buf.push(sample(1)); + buf.endSession(); + buf.takeNextBatch(); + expect(buf.discardBatch(1)).toBe(false); expect(buf.pendingCount).toBe(0); }); it("prependBatch() re-inserts a batch at the front of the queue", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); buf.endSession(); const batch = buf.takeNextBatch(); + expect(batch).not.toBeNull(); expect(buf.pendingCount).toBe(0); - buf.prependBatch(batch); + if (batch) buf.prependBatch(batch); expect(buf.pendingCount).toBe(1); - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + const next = buf.takeNextBatch(); + expect(next?.recordingId).toBe(1); + expect(next?.samples.map((s) => s.timeMs)).toEqual([1]); }); it("prependBatch() ignores empty batches", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.prependBatch([]); + buf.prependBatch({ recordingId: 1, samples: [] }); expect(buf.pendingCount).toBe(0); }); @@ -145,13 +186,13 @@ describe("createCursorTelemetryBuffer", () => { const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); for (let round = 1; round <= 2; round++) { - buf.startSession(); + buf.startSession(round); buf.push(sample(round)); expect(buf.endSession()).toBe(0); } expect(warn).not.toHaveBeenCalled(); - buf.startSession(); + buf.startSession(3); buf.push(sample(3)); const dropped = buf.endSession(); expect(dropped).toBe(1); @@ -168,7 +209,7 @@ describe("createCursorTelemetryBuffer", () => { // Fill the queue to the cap without dropping anything. for (let round = 1; round <= 2; round++) { - buf.startSession(); + buf.startSession(round); buf.push(sample(round)); buf.endSession(); } @@ -177,14 +218,14 @@ describe("createCursorTelemetryBuffer", () => { // Simulate a misuse where a retry prepends without first draining: // queue would grow to 3, so the oldest-trailing entry must be evicted. - buf.prependBatch([sample(99)]); + buf.prependBatch({ recordingId: 99, samples: [sample(99)] }); expect(buf.pendingCount).toBe(2); expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0]?.[0]).toMatch(/prependBatch trimmed 1 trailing batch/); // Front is the prepended batch; the preserved trailing batch is round 1. - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([99]); - expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]); + expect(buf.takeNextBatch()?.recordingId).toBe(99); + expect(buf.takeNextBatch()?.recordingId).toBe(1); expect(buf.pendingCount).toBe(0); warn.mockRestore(); @@ -198,7 +239,7 @@ describe("createCursorTelemetryBuffer", () => { maxPendingBatches: Number.NaN, }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); expect(() => buf.endSession()).not.toThrow(); expect(buf.pendingCount).toBe(1); @@ -207,7 +248,7 @@ describe("createCursorTelemetryBuffer", () => { maxActiveSamples: -5, maxPendingBatches: 0, }); - buf2.startSession(); + buf2.startSession(2); buf2.push(sample(2)); expect(() => buf2.endSession()).not.toThrow(); expect(buf2.pendingCount).toBe(1); @@ -215,16 +256,16 @@ describe("createCursorTelemetryBuffer", () => { it("reset() clears both active and pending state", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); - buf.startSession(); + buf.startSession(1); buf.push(sample(1)); buf.endSession(); - buf.startSession(); + buf.startSession(2); buf.push(sample(2)); buf.reset(); expect(buf.activeCount).toBe(0); expect(buf.pendingCount).toBe(0); - expect(buf.takeNextBatch()).toEqual([]); + expect(buf.takeNextBatch()).toBeNull(); }); }); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts index e97bab8..0c7e0e1 100644 --- a/src/lib/cursorTelemetryBuffer.ts +++ b/src/lib/cursorTelemetryBuffer.ts @@ -12,25 +12,39 @@ export interface CursorTelemetryPoint { cy: number; } +/** + * A completed batch of cursor samples, tagged with the recording id that + * produced them. The id is supplied at `startSession()` time and travels + * with the batch through the pending queue, retries, and discards. + */ +export interface CursorTelemetryBatch { + recordingId: number; + samples: CursorTelemetryPoint[]; +} + /** * Per-session cursor telemetry buffer with bounded memory. * - * Flow: `startSession()` → `push(point)` N times → `endSession()` enqueues - * the collected samples as a completed batch. The main process later - * drains batches in FIFO order via `takeNextBatch()` to persist them to - * disk, and can `prependBatch()` on write failure to retry without losing - * order. + * Flow: `startSession(recordingId)` → `push(point)` N times → `endSession()` + * enqueues the collected samples as a completed batch tagged with that + * `recordingId`. The main process later drains batches in FIFO order via + * `takeNextBatch()` to persist them to disk, and can `prependBatch()` on + * write failure to retry without losing order. A discard request keys on + * the recording id so an asynchronous "discard recording A" decision that + * arrives after recording B has already enqueued its batch still drops + * the right one. * * Memory is bounded by `maxActiveSamples` (ring buffer on the in-progress * batch) and `maxPendingBatches` (FIFO cap across completed batches). */ export interface CursorTelemetryBuffer { /** - * Begin a new recording session. Clears any in-progress active samples - * (without touching already-completed pending batches). Safe to call - * repeatedly — e.g. a rapid Stop → Record sequence. + * Begin a new recording session under the given `recordingId`. Clears + * any in-progress active samples (without touching already-completed + * pending batches). Safe to call repeatedly — e.g. a rapid Stop → + * Record sequence — and the most recent id wins. */ - startSession(): void; + startSession(recordingId: number): void; /** * Append a telemetry sample to the current active session. When the @@ -41,8 +55,8 @@ export interface CursorTelemetryBuffer { /** * Finalize the active session, moving its samples into the pending - * queue as a single batch. Empty sessions are dropped (no empty batch - * is enqueued). + * queue as a single batch tagged with the current recording id. Empty + * sessions are dropped (no empty batch is enqueued). * * If the pending queue would exceed `maxPendingBatches`, the oldest * batches are evicted to bound memory. A `console.warn` is emitted @@ -55,10 +69,10 @@ export interface CursorTelemetryBuffer { endSession(): number; /** - * Remove and return the oldest pending batch, or an empty array if - * the queue is empty. + * Remove and return the oldest pending batch, or `null` if the queue + * is empty. */ - takeNextBatch(): CursorTelemetryPoint[]; + takeNextBatch(): CursorTelemetryBatch | null; /** * Re-insert a batch at the front of the queue, preserving FIFO order @@ -71,14 +85,22 @@ export interface CursorTelemetryBuffer { * normal retry usage this trim is a no-op because the caller has just * removed the batch via `takeNextBatch()`. */ - prependBatch(batch: CursorTelemetryPoint[]): void; + prependBatch(batch: CursorTelemetryBatch): void; /** - * Drop the most recently enqueued pending batch. Used when a recording - * is discarded after `endSession()` but before it has been persisted. - * No-op on an empty queue. + * Drop the pending batch produced by the given `recordingId`. Used + * when a recording is discarded after its `endSession()` has run but + * before it has been persisted. Returns `true` if a batch was + * removed, `false` otherwise (no matching id, or the batch was + * already drained). + * + * Keying on the recording id (rather than "the latest pending batch") + * avoids a real bug: when finalizing a recording does asynchronous + * work like `fixWebmDuration`, a quick Stop → Record → Discard + * sequence can interleave such that the latest pending batch belongs + * to a *later* recording than the one being discarded. */ - discardLatestPending(): void; + discardBatch(recordingId: number): boolean; /** * Clear both the active and pending state. Intended for tests and @@ -121,11 +143,13 @@ export function createCursorTelemetryBuffer( const maxPending = sanitizeLimit(options.maxPendingBatches, DEFAULT_MAX_PENDING_BATCHES); let active: CursorTelemetryPoint[] = []; - let pending: CursorTelemetryPoint[][] = []; + let activeRecordingId: number | null = null; + let pending: CursorTelemetryBatch[] = []; return { - startSession() { + startSession(recordingId) { active = []; + activeRecordingId = recordingId; }, push(point) { active.push(point); @@ -135,14 +159,15 @@ export function createCursorTelemetryBuffer( }, endSession() { let dropped = 0; - if (active.length > 0) { - pending.push(active); + if (active.length > 0 && activeRecordingId !== null) { + pending.push({ recordingId: activeRecordingId, samples: active }); while (pending.length > maxPending) { pending.shift(); dropped++; } } active = []; + activeRecordingId = null; if (dropped > 0) { console.warn( `[cursorTelemetryBuffer] dropped ${dropped} pending batch(es) to stay within maxPendingBatches=${maxPending}`, @@ -151,10 +176,10 @@ export function createCursorTelemetryBuffer( return dropped; }, takeNextBatch() { - return pending.shift() ?? []; + return pending.shift() ?? null; }, prependBatch(batch) { - if (batch.length === 0) return; + if (batch.samples.length === 0) return; pending.unshift(batch); let dropped = 0; while (pending.length > maxPending) { @@ -167,11 +192,15 @@ export function createCursorTelemetryBuffer( ); } }, - discardLatestPending() { - pending.pop(); + discardBatch(recordingId) { + const idx = pending.findIndex((b) => b.recordingId === recordingId); + if (idx === -1) return false; + pending.splice(idx, 1); + return true; }, reset() { active = []; + activeRecordingId = null; pending = []; }, get activeCount() { From 6577a54418044891649e120678ec092704ad1ef0 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 28 Apr 2026 13:59:10 +0200 Subject: [PATCH 147/152] fix(exporter): normalize bare VP8/VP9 codec strings from web-demuxer --- src/lib/exporter/streamingDecoder.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 24b9844..c3bca3c 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -311,8 +311,16 @@ export class StreamingVideoDecoder { ); } + if (/^vp08$/i.test(decoderConfig.codec)) { + decoderConfig.codec = "vp8"; + } + if (/^vp09$/i.test(decoderConfig.codec)) { + decoderConfig.codec = "vp9"; + } + const codec = decoderConfig.codec.toLowerCase(); - const shouldPreferSoftwareDecode = codec.includes("av01") || codec.includes("av1"); + const shouldPreferSoftwareDecode = + codec.includes("av01") || codec.includes("av1") || codec.includes("vp09"); const segments = this.splitBySpeed( this.computeSegments(this.metadata.duration, trimRegions), speedRegions, From cae71ed49c56e9f0626ae17a2bae6f94fcec2a14 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 28 Apr 2026 14:08:01 +0200 Subject: [PATCH 148/152] fix(exporter): add codec normalization for bare avc1/h264 and logging --- src/lib/exporter/streamingDecoder.ts | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index c3bca3c..8cf0257 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -302,9 +302,12 @@ export class StreamingVideoDecoder { const decoderConfig = await this.demuxer.getDecoderConfig("video"); - // web-demuxer may return a bare "av01" for AV1 in WebM containers when the - // extradata isn't in the expected ISOBMFF format. WebCodecs requires the - // full parametrized form (e.g. "av01.0.05M.08"). + console.log("[StreamingVideoDecoder] decoderConfig.codec:", decoderConfig.codec); + console.log("[StreamingVideoDecoder] decoderConfig.description:", decoderConfig.description); + + // web-demuxer may return bare four-character code strings ("av01", "vp08", + // "vp09", "avc1") that WebCodecs rejects. Normalize them to the short or + // full parametrized forms that VideoDecoder accepts. if (/^av01$/i.test(decoderConfig.codec)) { decoderConfig.codec = buildAV1CodecString( decoderConfig.description as BufferSource | undefined, @@ -318,9 +321,19 @@ export class StreamingVideoDecoder { decoderConfig.codec = "vp9"; } + if (/^avc1$/i.test(decoderConfig.codec)) { + decoderConfig.codec = "avc1.640033"; + } + if (/^h264$/i.test(decoderConfig.codec)) { + decoderConfig.codec = "avc1.640033"; + } + const codec = decoderConfig.codec.toLowerCase(); const shouldPreferSoftwareDecode = - codec.includes("av01") || codec.includes("av1") || codec.includes("vp09"); + codec.includes("av01") || + codec.includes("av1") || + codec.includes("vp09") || + codec.includes("vp9"); const segments = this.splitBySpeed( this.computeSegments(this.metadata.duration, trimRegions), speedRegions, @@ -351,6 +364,10 @@ export class StreamingVideoDecoder { } }, error: (e: DOMException) => { + console.warn( + `[StreamingVideoDecoder] decoder error for codec "${decoderConfig.codec}":`, + e.message, + ); decodeError = new Error(`VideoDecoder error: ${e.message}`); if (frameResolve) { const resolve = frameResolve; @@ -367,6 +384,17 @@ export class StreamingVideoDecoder { : decoderConfig; try { + const support = await VideoDecoder.isConfigSupported(preferredDecoderConfig); + console.log( + `[StreamingVideoDecoder] isConfigSupported for "${preferredDecoderConfig.codec}":`, + support.supported, + ); + if (!support.supported) { + throw new DOMException( + `Unsupported codec: ${preferredDecoderConfig.codec}`, + "NotSupportedError", + ); + } this.decoder.configure(preferredDecoderConfig); } catch (error) { if (!shouldPreferSoftwareDecode) { From f9401f051c68392fe7bc4483702d518272057347 Mon Sep 17 00:00:00 2001 From: FabLrc Date: Tue, 28 Apr 2026 14:13:34 +0200 Subject: [PATCH 149/152] fix(exporter): fall back to avc1.640033 for unsupported H.264 codec strings --- src/lib/exporter/streamingDecoder.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 8cf0257..dd4df7b 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -390,18 +390,22 @@ export class StreamingVideoDecoder { support.supported, ); if (!support.supported) { - throw new DOMException( - `Unsupported codec: ${preferredDecoderConfig.codec}`, - "NotSupportedError", - ); + throw new Error(`Unsupported codec: ${preferredDecoderConfig.codec}`); } this.decoder.configure(preferredDecoderConfig); } catch (error) { - if (!shouldPreferSoftwareDecode) { + if (shouldPreferSoftwareDecode) { + this.decoder.configure(decoderConfig); + } else if (/^avc1/i.test(codec)) { + const fallback = { ...decoderConfig, codec: "avc1.640033" }; + console.warn( + `[StreamingVideoDecoder] codec "${codec}" unsupported, ` + + `falling back to "${fallback.codec}"`, + ); + this.decoder.configure(fallback); + } else { throw error; } - // Fall back to default decoder config if software preference isn't supported. - this.decoder.configure(decoderConfig); } const getNextFrame = (): Promise => { From 0768c449d756cc9dd1f36467021e3531a40f3a18 Mon Sep 17 00:00:00 2001 From: Marc Diaz Date: Wed, 29 Apr 2026 22:36:49 -0400 Subject: [PATCH 150/152] feat: all changes --- src/components/video-editor/VideoEditor.tsx | 12 +++++ ...st.ts => useCameraDevices.browser.test.ts} | 46 +++++++----------- .../tutorialHelpTranslations.test.ts | 2 + src/lib/blurEffects.test.ts | 2 +- src/lib/exporter/gifExporter.ts | 7 ++- src/lib/exporter/streamingDecoder.ts | 22 ++------- src/lib/exporter/types.ts | 1 + src/lib/exporter/videoExporter.ts | 6 ++- tests/fixtures/sample-inflated-duration.webm | Bin 0 -> 1252 bytes 9 files changed, 48 insertions(+), 50 deletions(-) rename src/hooks/{useCameraDevices.test.ts => useCameraDevices.browser.test.ts} (72%) create mode 100644 tests/fixtures/sample-inflated-duration.webm diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 6d21d13..f33611b 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -1409,6 +1409,12 @@ export default function VideoEditor() { const timestamp = Date.now(); const fileName = `export-${timestamp}.gif`; + if (result.warnings) { + for (const warning of result.warnings) { + toast.warning(warning); + } + } + const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName); if (saveResult.canceled) { @@ -1543,6 +1549,12 @@ export default function VideoEditor() { const timestamp = Date.now(); const fileName = `export-${timestamp}.mp4`; + if (result.warnings) { + for (const warning of result.warnings) { + toast.warning(warning); + } + } + const saveResult = await window.electronAPI.saveExportedVideo(arrayBuffer, fileName); if (saveResult.canceled) { diff --git a/src/hooks/useCameraDevices.test.ts b/src/hooks/useCameraDevices.browser.test.ts similarity index 72% rename from src/hooks/useCameraDevices.test.ts rename to src/hooks/useCameraDevices.browser.test.ts index 5ca21bc..71709fd 100644 --- a/src/hooks/useCameraDevices.test.ts +++ b/src/hooks/useCameraDevices.browser.test.ts @@ -2,40 +2,26 @@ import { act, renderHook, waitFor } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useCameraDevices } from "./useCameraDevices"; -// Mock navigator.mediaDevices const mockDevices = [ { kind: "videoinput", deviceId: "cam1", label: "Camera 1", groupId: "group1" }, { kind: "videoinput", deviceId: "cam2", label: "Camera 2", groupId: "group1" }, { kind: "audioinput", deviceId: "mic1", label: "Mic 1", groupId: "group2" }, ]; -const mockGetUserMedia = vi.fn().mockResolvedValue({ - getTracks: () => [{ stop: vi.fn() }], -}); - -const mockEnumerateDevices = vi.fn().mockResolvedValue(mockDevices); - -Object.defineProperty(global.navigator, "mediaDevices", { - value: { - enumerateDevices: mockEnumerateDevices, - getUserMedia: mockGetUserMedia, - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - }, - configurable: true, -}); - describe("useCameraDevices", () => { beforeEach(() => { - vi.clearAllMocks(); - mockEnumerateDevices.mockResolvedValue(mockDevices); - mockGetUserMedia.mockResolvedValue({ + vi.spyOn(navigator.mediaDevices, "enumerateDevices").mockResolvedValue( + mockDevices as MediaDeviceInfo[], + ); + vi.spyOn(navigator.mediaDevices, "getUserMedia").mockResolvedValue({ getTracks: () => [{ stop: vi.fn() }], - }); + } as unknown as MediaStream); + vi.spyOn(navigator.mediaDevices, "addEventListener"); + vi.spyOn(navigator.mediaDevices, "removeEventListener"); }); afterEach(() => { - vi.resetAllMocks(); + vi.restoreAllMocks(); }); it("should list video input devices", async () => { @@ -58,9 +44,9 @@ describe("useCameraDevices", () => { }); it("should use device ID as fallback label when label is missing", async () => { - mockEnumerateDevices.mockResolvedValueOnce([ + vi.mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValueOnce([ { kind: "videoinput", deviceId: "cam1abc123456", label: "", groupId: "group1" }, - ]); + ] as MediaDeviceInfo[]); const { result } = renderHook(() => useCameraDevices(true)); @@ -68,11 +54,13 @@ describe("useCameraDevices", () => { expect(result.current.devices[0]?.label).toBe("Camera cam1abc1"); }); - expect(mockGetUserMedia).not.toHaveBeenCalled(); + expect(navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled(); }); it("should set error state when enumeration fails", async () => { - mockEnumerateDevices.mockRejectedValueOnce(new Error("Permission denied")); + vi.mocked(navigator.mediaDevices.enumerateDevices).mockRejectedValueOnce( + new Error("Permission denied"), + ); const { result } = renderHook(() => useCameraDevices(true)); @@ -91,13 +79,13 @@ describe("useCameraDevices", () => { expect(result.current.selectedDeviceId).toBe("cam1"); }); - // Simulate cam1 being unplugged — only cam2 remains const cam2Only = [ { kind: "videoinput", deviceId: "cam2", label: "Camera 2", groupId: "group1" }, ]; - mockEnumerateDevices.mockResolvedValueOnce(cam2Only); + vi.mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValueOnce( + cam2Only as MediaDeviceInfo[], + ); - // Trigger devicechange event via the registered handler const devicechangeHandler = ( navigator.mediaDevices.addEventListener as ReturnType ).mock.calls[0]?.[1] as (() => void) | undefined; diff --git a/src/i18n/__tests__/tutorialHelpTranslations.test.ts b/src/i18n/__tests__/tutorialHelpTranslations.test.ts index fcfa9d3..ac45daa 100644 --- a/src/i18n/__tests__/tutorialHelpTranslations.test.ts +++ b/src/i18n/__tests__/tutorialHelpTranslations.test.ts @@ -6,6 +6,7 @@ import frDialogs from "@/i18n/locales/fr/dialogs.json"; import koKRDialogs from "@/i18n/locales/ko-KR/dialogs.json"; import trDialogs from "@/i18n/locales/tr/dialogs.json"; import zhCNDialogs from "@/i18n/locales/zh-CN/dialogs.json"; +import zhTWDialogs from "@/i18n/locales/zh-TW/dialogs.json"; const tutorialHelpKeys = [ "triggerLabel", @@ -35,6 +36,7 @@ const keysThatMayBeEmpty = new Set<(typeof tutorialHelpKeys)[number]>(["step1Des const dialogsByLocale = { en: enDialogs, "zh-CN": zhCNDialogs, + "zh-TW": zhTWDialogs, es: esDialogs, fr: frDialogs, tr: trDialogs, diff --git a/src/lib/blurEffects.test.ts b/src/lib/blurEffects.test.ts index 4797e69..1a6a9c9 100644 --- a/src/lib/blurEffects.test.ts +++ b/src/lib/blurEffects.test.ts @@ -75,6 +75,6 @@ describe("blur color helpers", () => { intensity: 12, blockSize: 12, }), - ).toBe("rgba(0, 0, 0, 0.18)"); + ).toBe("rgba(0, 0, 0, 0.56)"); }); }); diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 46ac6a0..ae7c73f 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -117,6 +117,9 @@ export class GifExporter { async export(): Promise { let webcamFrameQueue: AsyncVideoFrameQueue | null = null; + const warnings: string[] = []; + const onWarning = (message: string) => warnings.push(message); + try { const platform = await getPlatform(); @@ -219,6 +222,7 @@ export class GifExporter { } queue.enqueue(webcamFrame); }, + onWarning, ) .catch((error) => { webcamDecodeError = error instanceof Error ? error : new Error(String(error)); @@ -278,6 +282,7 @@ export class GifExporter { webcamFrame?.close(); } }, + onWarning, ); if (this.cancelled) { @@ -324,7 +329,7 @@ export class GifExporter { this.gif!.render(); }); - return { success: true, blob }; + return { success: true, blob, warnings: warnings.length > 0 ? warnings : undefined }; } catch (error) { console.error("GIF Export error:", error); return { diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index b64c5ab..f6f5411 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -292,6 +292,7 @@ export class StreamingVideoDecoder { trimRegions: TrimRegion[] | undefined, speedRegions: SpeedRegion[] | undefined, onFrame: OnFrameCallback, + onWarning?: (message: string) => void, ): Promise { if (!this.demuxer || !this.metadata) { throw new Error("Must call loadMetadata() before decodeAll()"); @@ -563,8 +564,6 @@ export class StreamingVideoDecoder { } this.decoder = null; - const isWindows = typeof navigator !== "undefined" && /Windows/.test(navigator.userAgent); - if ( shouldFailDecodeEndedEarly({ cancelled: this.cancelled, @@ -575,22 +574,9 @@ export class StreamingVideoDecoder { ) { const decodedAtLabel = lastDecodedFrameSec === null ? "no decoded frame" : `${lastDecodedFrameSec.toFixed(3)}s`; - const decodeGapSec = - lastDecodedFrameSec === null ? Infinity : requiredEndSec - lastDecodedFrameSec; - - // On Windows, tolerate a small decode gap: up to 10% of required duration, capped at 3 seconds. - const maxToleratedGap = Math.min(3.0, requiredEndSec * 0.1); - - if (isWindows && lastDecodedFrameSec !== null && decodeGapSec <= maxToleratedGap) { - console.warn( - `[StreamingVideoDecoder] Decode ended early on Windows with a gap of ${decodeGapSec.toFixed(2)}s ` + - `(max tolerated: ${maxToleratedGap.toFixed(2)}s) – proceeding anyway.`, - ); - } else { - throw new Error( - `Video decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s).`, - ); - } + const message = `Decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s) – export may be slightly shorter than expected.`; + console.warn(`[StreamingVideoDecoder] ${message}`); + onWarning?.(message); } } diff --git a/src/lib/exporter/types.ts b/src/lib/exporter/types.ts index b6e08e8..3873341 100644 --- a/src/lib/exporter/types.ts +++ b/src/lib/exporter/types.ts @@ -19,6 +19,7 @@ export interface ExportResult { success: boolean; blob?: Blob; error?: string; + warnings?: string[]; } export interface VideoFrameData { diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 44c1b88..9984edc 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -107,6 +107,8 @@ export class VideoExporter { let webcamDecodeError: Error | null = null; let webcamDecodePromise: Promise | null = null; let webcamDecoder: StreamingVideoDecoder | null = null; + const warnings: string[] = []; + const onWarning = (message: string) => warnings.push(message); this.cleanup(); this.cancelled = false; @@ -194,6 +196,7 @@ export class VideoExporter { } queue.enqueue(webcamFrame); }, + onWarning, ) .catch((error) => { webcamDecodeError = error instanceof Error ? error : new Error(String(error)); @@ -298,6 +301,7 @@ export class VideoExporter { webcamFrame?.close(); } }, + onWarning, ); if (this.cancelled) { @@ -354,7 +358,7 @@ export class VideoExporter { } const blob = await muxer.finalize(); - return { success: true, blob }; + return { success: true, blob, warnings: warnings.length > 0 ? warnings : undefined }; } finally { stopWebcamDecode = true; webcamFrameQueue?.destroy(); diff --git a/tests/fixtures/sample-inflated-duration.webm b/tests/fixtures/sample-inflated-duration.webm new file mode 100644 index 0000000000000000000000000000000000000000..a90d19eecffc32af266c963140b08c67c1b5ea1b GIT binary patch literal 1252 zcmcK4Ur19?7y$6^ZYu>J`o~H_Xrxa@V>2e%)7jltt-5M%vp+9wu3S-;FkQZN&;F-I zSrFA$PKFPfj8#_TUT%o6o`ZxS8uXAw&`KzRWZmoT!M@%*+U>&)boOJ%JAe$FgWg)#6a<`Bb#uv+dAxy zE_(ts5DdQcOC$cZPX&sTe<$COoA=Ajo6So1h70Fr_}qLnHQfkjv$rYOAP(ea8k_{Q27r^6ZMlt#$5a;#*o&$k5|MLttd1AEp!z{v zpSz*@fq(K}tG-`Cbu@bB2JFXGcVw`qWS8W*_!9PaEFagAXRrPTyM*$!e&$~6lgt78 zlIpe$a@{WG1bZKITt}BzotJR_GIJ66I*aVeyd;Bth~*RP1?GT#MRjWl`Fg@!!X9BR rVqakHMP5_O+<^TsbHIL0by8Y$<(?TUTz^90`LKMSeG79_WvcxH8h3FB literal 0 HcmV?d00001 From 786165208ff2a4c5588f5e8d8cb5144634aa51bf Mon Sep 17 00:00:00 2001 From: Marc Diaz Date: Wed, 29 Apr 2026 22:45:41 -0400 Subject: [PATCH 151/152] misc: remove misc changes --- package-lock.json | 12 ++--- ...owser.test.ts => useCameraDevices.test.ts} | 46 ++++++++++++------- .../tutorialHelpTranslations.test.ts | 2 - src/lib/blurEffects.test.ts | 2 +- src/lib/exporter/streamingDecoder.test.ts | 14 ------ vitest.config.ts | 3 +- 6 files changed, 36 insertions(+), 43 deletions(-) rename src/hooks/{useCameraDevices.browser.test.ts => useCameraDevices.test.ts} (72%) diff --git a/package-lock.json b/package-lock.json index ed0c9af..ba40beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7222,15 +7222,13 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.21", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", - "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.15.tgz", + "integrity": "sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==", "dev": true, + "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/bcrypt-pbkdf": { diff --git a/src/hooks/useCameraDevices.browser.test.ts b/src/hooks/useCameraDevices.test.ts similarity index 72% rename from src/hooks/useCameraDevices.browser.test.ts rename to src/hooks/useCameraDevices.test.ts index 71709fd..5ca21bc 100644 --- a/src/hooks/useCameraDevices.browser.test.ts +++ b/src/hooks/useCameraDevices.test.ts @@ -2,26 +2,40 @@ import { act, renderHook, waitFor } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useCameraDevices } from "./useCameraDevices"; +// Mock navigator.mediaDevices const mockDevices = [ { kind: "videoinput", deviceId: "cam1", label: "Camera 1", groupId: "group1" }, { kind: "videoinput", deviceId: "cam2", label: "Camera 2", groupId: "group1" }, { kind: "audioinput", deviceId: "mic1", label: "Mic 1", groupId: "group2" }, ]; +const mockGetUserMedia = vi.fn().mockResolvedValue({ + getTracks: () => [{ stop: vi.fn() }], +}); + +const mockEnumerateDevices = vi.fn().mockResolvedValue(mockDevices); + +Object.defineProperty(global.navigator, "mediaDevices", { + value: { + enumerateDevices: mockEnumerateDevices, + getUserMedia: mockGetUserMedia, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }, + configurable: true, +}); + describe("useCameraDevices", () => { beforeEach(() => { - vi.spyOn(navigator.mediaDevices, "enumerateDevices").mockResolvedValue( - mockDevices as MediaDeviceInfo[], - ); - vi.spyOn(navigator.mediaDevices, "getUserMedia").mockResolvedValue({ + vi.clearAllMocks(); + mockEnumerateDevices.mockResolvedValue(mockDevices); + mockGetUserMedia.mockResolvedValue({ getTracks: () => [{ stop: vi.fn() }], - } as unknown as MediaStream); - vi.spyOn(navigator.mediaDevices, "addEventListener"); - vi.spyOn(navigator.mediaDevices, "removeEventListener"); + }); }); afterEach(() => { - vi.restoreAllMocks(); + vi.resetAllMocks(); }); it("should list video input devices", async () => { @@ -44,9 +58,9 @@ describe("useCameraDevices", () => { }); it("should use device ID as fallback label when label is missing", async () => { - vi.mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValueOnce([ + mockEnumerateDevices.mockResolvedValueOnce([ { kind: "videoinput", deviceId: "cam1abc123456", label: "", groupId: "group1" }, - ] as MediaDeviceInfo[]); + ]); const { result } = renderHook(() => useCameraDevices(true)); @@ -54,13 +68,11 @@ describe("useCameraDevices", () => { expect(result.current.devices[0]?.label).toBe("Camera cam1abc1"); }); - expect(navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled(); + expect(mockGetUserMedia).not.toHaveBeenCalled(); }); it("should set error state when enumeration fails", async () => { - vi.mocked(navigator.mediaDevices.enumerateDevices).mockRejectedValueOnce( - new Error("Permission denied"), - ); + mockEnumerateDevices.mockRejectedValueOnce(new Error("Permission denied")); const { result } = renderHook(() => useCameraDevices(true)); @@ -79,13 +91,13 @@ describe("useCameraDevices", () => { expect(result.current.selectedDeviceId).toBe("cam1"); }); + // Simulate cam1 being unplugged — only cam2 remains const cam2Only = [ { kind: "videoinput", deviceId: "cam2", label: "Camera 2", groupId: "group1" }, ]; - vi.mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValueOnce( - cam2Only as MediaDeviceInfo[], - ); + mockEnumerateDevices.mockResolvedValueOnce(cam2Only); + // Trigger devicechange event via the registered handler const devicechangeHandler = ( navigator.mediaDevices.addEventListener as ReturnType ).mock.calls[0]?.[1] as (() => void) | undefined; diff --git a/src/i18n/__tests__/tutorialHelpTranslations.test.ts b/src/i18n/__tests__/tutorialHelpTranslations.test.ts index ac45daa..fcfa9d3 100644 --- a/src/i18n/__tests__/tutorialHelpTranslations.test.ts +++ b/src/i18n/__tests__/tutorialHelpTranslations.test.ts @@ -6,7 +6,6 @@ import frDialogs from "@/i18n/locales/fr/dialogs.json"; import koKRDialogs from "@/i18n/locales/ko-KR/dialogs.json"; import trDialogs from "@/i18n/locales/tr/dialogs.json"; import zhCNDialogs from "@/i18n/locales/zh-CN/dialogs.json"; -import zhTWDialogs from "@/i18n/locales/zh-TW/dialogs.json"; const tutorialHelpKeys = [ "triggerLabel", @@ -36,7 +35,6 @@ const keysThatMayBeEmpty = new Set<(typeof tutorialHelpKeys)[number]>(["step1Des const dialogsByLocale = { en: enDialogs, "zh-CN": zhCNDialogs, - "zh-TW": zhTWDialogs, es: esDialogs, fr: frDialogs, tr: trDialogs, diff --git a/src/lib/blurEffects.test.ts b/src/lib/blurEffects.test.ts index 1a6a9c9..4797e69 100644 --- a/src/lib/blurEffects.test.ts +++ b/src/lib/blurEffects.test.ts @@ -75,6 +75,6 @@ describe("blur color helpers", () => { intensity: 12, blockSize: 12, }), - ).toBe("rgba(0, 0, 0, 0.56)"); + ).toBe("rgba(0, 0, 0, 0.18)"); }); }); diff --git a/src/lib/exporter/streamingDecoder.test.ts b/src/lib/exporter/streamingDecoder.test.ts index 45be92b..55b9123 100644 --- a/src/lib/exporter/streamingDecoder.test.ts +++ b/src/lib/exporter/streamingDecoder.test.ts @@ -83,18 +83,4 @@ describe("shouldFailDecodeEndedEarly", () => { }), ).toBe(true); }); - - it("does not fail when decoder reached stream end but container tail is large (inflated metadata)", () => { - // Real case: ~20min video where container reports 1234s but actual stream - // ends at 1226s. Decoder correctly stops at 1226s (= streamDurationSec). - // The 8s tail is container metadata inflation, not a real decode failure. - expect( - shouldFailDecodeEndedEarly({ - cancelled: false, - lastDecodedFrameSec: 1226, - requiredEndSec: 1234, - streamDurationSec: 1226, - }), - ).toBe(false); - }); }); diff --git a/vitest.config.ts b/vitest.config.ts index 8ad92d3..ea60216 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,9 +4,8 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - environment: "node", + environment: "jsdom", include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], - exclude: ["src/**/*.browser.test.{ts,tsx}", "node_modules"], }, resolve: { alias: { From 93466fdda1df6963a7b6ee3255a904afa6bb37dc Mon Sep 17 00:00:00 2001 From: Marc Diaz Date: Wed, 29 Apr 2026 22:52:15 -0400 Subject: [PATCH 152/152] fix: add max duration --- src/lib/exporter/streamingDecoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index f6f5411..0506881 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -68,7 +68,7 @@ type EarlyDecodeEndCheck = { }; const EARLY_DECODE_END_THRESHOLD_SEC = 1; -const METADATA_TAIL_TOLERANCE_SEC = 1.5; +const METADATA_TAIL_TOLERANCE_SEC = 2; const STREAM_DURATION_MATCH_TOLERANCE_SEC = 0.25; const DURATION_DIVERGENCE_THRESHOLD_SEC = 1.5; // Fallback upper bound for the packet scan when no reliable duration hint is