From 3895ca985f845dd99c9f0af83af7d5aec3c9da20 Mon Sep 17 00:00:00 2001 From: Ishan Panta Date: Fri, 3 Apr 2026 08:37:16 +0545 Subject: [PATCH 001/228] [add] extend speed options with higher presets and custom speed input add 3x, 4x, 5x speed presets and a custom playback speed input field that accepts any integer value up to 16x. change PlaybackSpeed type from a fixed union to number with min/max constants and clamp utility. update project persistence to validate any speed in range instead of exact value matching. add i18n keys for en, es, zh-CN. closes #252 --- src/components/video-editor/SettingsPanel.tsx | 90 ++++++++++++++++++- .../video-editor/projectPersistence.ts | 15 ++-- src/components/video-editor/types.ts | 14 ++- src/i18n/locales/en/settings.json | 4 +- src/i18n/locales/es/settings.json | 4 +- src/i18n/locales/zh-CN/settings.json | 4 +- 6 files changed, 117 insertions(+), 14 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index f5afe35..cdb00ce 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -53,7 +53,70 @@ import type { WebcamLayoutPreset, ZoomDepth, } from "./types"; -import { SPEED_OPTIONS } from "./types"; +import { MAX_PLAYBACK_SPEED, SPEED_OPTIONS } from "./types"; + +function CustomSpeedInput({ + value, + onChange, + onError, +}: { + value: number; + onChange: (val: number) => void; + onError: () => void; +}) { + const isPreset = SPEED_OPTIONS.some((o) => o.speed === value); + const [draft, setDraft] = useState(isPreset ? "" : String(Math.round(value))); + const [isFocused, setIsFocused] = useState(false); + + const prevValue = useRef(value); + if (!isFocused && prevValue.current !== value) { + prevValue.current = value; + setDraft(isPreset ? "" : String(Math.round(value))); + } + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const digits = e.target.value.replace(/\D/g, ""); + if (digits === "") { + setDraft(""); + return; + } + const num = Number(digits); + if (num > MAX_PLAYBACK_SPEED) { + onError(); + return; + } + setDraft(digits); + if (num >= 1) onChange(num); + }, + [onChange, onError], + ); + + const handleBlur = useCallback(() => { + setIsFocused(false); + if (!draft || Number(draft) < 1) { + setDraft(isPreset ? "" : String(Math.round(value))); + } + }, [draft, isPreset, value]); + + return ( +
+ setIsFocused(true)} + onChange={handleChange} + onBlur={handleBlur} + onKeyDown={(e) => e.key === "Enter" && (e.target as HTMLInputElement).blur()} + className="w-12 bg-white/5 border border-white/10 rounded-md px-1 py-0.5 text-[11px] font-semibold text-[#d97706] text-center focus:outline-none focus:border-[#d97706]/40" + /> + × +
+ ); +} const WALLPAPER_COUNT = 18; const WALLPAPER_RELATIVE = Array.from( @@ -537,7 +600,7 @@ export function SettingsPanel({ )} -
+
{SPEED_OPTIONS.map((option) => { const isActive = selectedSpeedValue === option.speed; return ( @@ -562,6 +625,29 @@ export function SettingsPanel({ ); })}
+
+
+ + {t("speed.customPlaybackSpeed")} + + {selectedSpeedId ? ( + onSpeedChange?.(val)} + onError={() => toast.error(t("speed.maxSpeedError"))} + /> + ) : ( +
+
+ -- +
+ × +
+ )} +
+
{!selectedSpeedId && (

{t("speed.selectRegion")}

)} diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 99f1bba..bfe6972 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -5,6 +5,7 @@ import { ASPECT_RATIOS, type AspectRatio } from "@/utils/aspectRatioUtils"; import { type AnnotationRegion, type CropRegion, + clampPlaybackSpeed, DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, @@ -14,6 +15,8 @@ import { DEFAULT_WEBCAM_LAYOUT_PRESET, DEFAULT_WEBCAM_POSITION, DEFAULT_ZOOM_DEPTH, + MAX_PLAYBACK_SPEED, + MIN_PLAYBACK_SPEED, type SpeedRegion, type TrimRegion, type WebcamLayoutPreset, @@ -219,14 +222,10 @@ export function normalizeProjectEditor(editor: Partial): Pro const endMs = Math.max(startMs + 1, rawEnd); const speed = - region.speed === 0.25 || - region.speed === 0.5 || - region.speed === 0.75 || - region.speed === 1.25 || - region.speed === 1.5 || - region.speed === 1.75 || - region.speed === 2 - ? region.speed + isFiniteNumber(region.speed) && + region.speed >= MIN_PLAYBACK_SPEED && + region.speed <= MAX_PLAYBACK_SPEED + ? clampPlaybackSpeed(region.speed) : DEFAULT_PLAYBACK_SPEED; return { diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index ce49f8e..d52e60a 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -132,7 +132,16 @@ export const DEFAULT_CROP_REGION: CropRegion = { height: 1, }; -export type PlaybackSpeed = 0.25 | 0.5 | 0.75 | 1.25 | 1.5 | 1.75 | 2; +export type PlaybackSpeed = number; + +export const MIN_PLAYBACK_SPEED = 0.1; +// Anything above 16x causes the playhead to stall during preview +// due to the video decoder not being able to keep up. +export const MAX_PLAYBACK_SPEED = 16; + +export function clampPlaybackSpeed(speed: number): PlaybackSpeed { + return Math.round(Math.min(MAX_PLAYBACK_SPEED, Math.max(MIN_PLAYBACK_SPEED, speed)) * 100) / 100; +} export interface SpeedRegion { id: string; @@ -149,6 +158,9 @@ export const SPEED_OPTIONS: Array<{ speed: PlaybackSpeed; label: string }> = [ { speed: 1.5, label: "1.5×" }, { speed: 1.75, label: "1.75×" }, { speed: 2, label: "2×" }, + { speed: 3, label: "3×" }, + { speed: 4, label: "4×" }, + { speed: 5, label: "5×" }, ]; export const DEFAULT_PLAYBACK_SPEED: PlaybackSpeed = 1.5; diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 36b7462..ad5f308 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -7,7 +7,9 @@ "speed": { "playbackSpeed": "Playback Speed", "selectRegion": "Select a speed region to adjust", - "deleteRegion": "Delete Speed Region" + "deleteRegion": "Delete Speed Region", + "customPlaybackSpeed": "Custom Playback Speed", + "maxSpeedError": "Speed can't go higher than 16×" }, "trim": { "deleteRegion": "Delete Trim Region" diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 4674480..f4c2d42 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -7,7 +7,9 @@ "speed": { "playbackSpeed": "Velocidad de reproducción", "selectRegion": "Selecciona una región de velocidad para ajustar", - "deleteRegion": "Eliminar región de velocidad" + "deleteRegion": "Eliminar región de velocidad", + "customPlaybackSpeed": "Velocidad personalizada", + "maxSpeedError": "La velocidad no puede superar 16×" }, "trim": { "deleteRegion": "Eliminar región de recorte" diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index 41bf55b..d38291e 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -7,7 +7,9 @@ "speed": { "playbackSpeed": "播放速度", "selectRegion": "选择要调整的速度区域", - "deleteRegion": "删除速度区域" + "deleteRegion": "删除速度区域", + "customPlaybackSpeed": "自定义播放速度", + "maxSpeedError": "速度不能超过 16×" }, "trim": { "deleteRegion": "删除剪辑区域" From 2b471783c084800acd67c37e8a74439c1db864a1 Mon Sep 17 00:00:00 2001 From: Adam <69064669+abres33@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:00:36 -0500 Subject: [PATCH 002/228] feat: add Cancel Recording button to HUD --- src/components/launch/LaunchWindow.tsx | 15 +++++++++++++++ src/hooks/useScreenRecorder.ts | 13 +++++++++++++ src/i18n/locales/en/launch.json | 1 + src/i18n/locales/es/launch.json | 1 + src/i18n/locales/zh-CN/launch.json | 1 + 5 files changed, 31 insertions(+) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index f1b66b8..d1185e8 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -5,6 +5,7 @@ import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; import { FiMinus, FiX } from "react-icons/fi"; import { + MdCancel, MdMic, MdMicOff, MdMonitor, @@ -43,6 +44,7 @@ const ICON_CONFIG = { webcamOff: { icon: MdVideocamOff, size: ICON_SIZE }, stop: { icon: FaRegStopCircle, size: ICON_SIZE }, restart: { icon: MdRestartAlt, size: ICON_SIZE }, + cancel: { icon: MdCancel, size: ICON_SIZE }, record: { icon: BsRecordCircle, size: ICON_SIZE }, videoFile: { icon: MdVideoFile, size: ICON_SIZE }, folder: { icon: FaFolderOpen, size: ICON_SIZE }, @@ -79,6 +81,7 @@ export function LaunchWindow() { recording, toggleRecording, restartRecording, + cancelRecording, microphoneEnabled, setMicrophoneEnabled, microphoneDeviceId, @@ -477,6 +480,18 @@ export function LaunchWindow() { )} + {/* Cancel recording */} + {recording && ( + + + + )} + {/* Open video file */} + + + + +
+ + {recording && ( + + + + )} + {/* Restart recording */} {recording && ( diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 0c418c1..8e92962 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -41,7 +41,10 @@ const WEBCAM_TARGET_FRAME_RATE = 30; type UseScreenRecorderReturn = { recording: boolean; + paused: boolean; + elapsedSeconds: number; toggleRecording: () => void; + togglePaused: () => void; restartRecording: () => void; microphoneEnabled: boolean; setMicrophoneEnabled: (enabled: boolean) => void; @@ -85,6 +88,8 @@ function createRecorderHandle(stream: MediaStream, options: MediaRecorderOptions export function useScreenRecorder(): UseScreenRecorderReturn { const t = useScopedT("editor"); const [recording, setRecording] = useState(false); + const [paused, setPaused] = useState(false); + const [elapsedSeconds, setElapsedSeconds] = useState(0); const [microphoneEnabled, setMicrophoneEnabled] = useState(false); const [microphoneDeviceId, setMicrophoneDeviceId] = useState(undefined); const [webcamDeviceId, setWebcamDeviceId] = useState(undefined); @@ -97,13 +102,22 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const microphoneStream = useRef(null); const webcamStream = useRef(null); const mixingContext = useRef(null); - const startTime = useRef(0); const recordingId = useRef(0); + const accumulatedDurationMs = useRef(0); + const segmentStartedAt = useRef(null); const finalizingRecordingId = useRef(null); const allowAutoFinalize = useRef(false); const discardRecordingId = useRef(null); const restarting = useRef(false); + const getRecordingDurationMs = useCallback(() => { + const segmentDuration = + screenRecorder.current?.recorder.state === "recording" && segmentStartedAt.current + ? Date.now() - segmentStartedAt.current + : 0; + return accumulatedDurationMs.current + segmentDuration; + }, []); + const selectMimeType = () => { const preferred = [ "video/webm;codecs=av1", @@ -202,6 +216,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { teardownMedia(); setRecording(false); + setPaused(false); + setElapsedSeconds(0); + accumulatedDurationMs.current = 0; + segmentStartedAt.current = null; window.electronAPI?.setRecordingState(false); void (async () => { @@ -273,7 +291,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } const activeWebcamRecorder = webcamRecorder.current; - const duration = Date.now() - startTime.current; + const duration = getRecordingDurationMs(); const activeRecordingId = recordingId.current; finalizeRecording( @@ -283,7 +301,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { activeRecordingId, ); - if (activeScreenRecorder.recorder.state === "recording") { + if ( + activeScreenRecorder.recorder.state === "recording" || + activeScreenRecorder.recorder.state === "paused" + ) { try { activeScreenRecorder.recorder.stop(); } catch { @@ -291,7 +312,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } if (activeWebcamRecorder) { - if (activeWebcamRecorder.recorder.state === "recording") { + if ( + activeWebcamRecorder.recorder.state === "recording" || + activeWebcamRecorder.recorder.state === "paused" + ) { try { activeWebcamRecorder.recorder.stop(); } catch { @@ -316,14 +340,20 @@ export function useScreenRecorder(): UseScreenRecorderReturn { restarting.current = false; discardRecordingId.current = null; - if (screenRecorder.current?.recorder.state === "recording") { + if ( + screenRecorder.current?.recorder.state === "recording" || + screenRecorder.current?.recorder.state === "paused" + ) { try { screenRecorder.current.recorder.stop(); } catch { // Ignore recorder teardown errors during cleanup. } } - if (webcamRecorder.current?.recorder.state === "recording") { + if ( + webcamRecorder.current?.recorder.state === "recording" || + webcamRecorder.current?.recorder.state === "paused" + ) { try { webcamRecorder.current.recorder.stop(); } catch { @@ -518,9 +548,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } recordingId.current = Date.now(); - startTime.current = recordingId.current; + accumulatedDurationMs.current = 0; + segmentStartedAt.current = Date.now(); allowAutoFinalize.current = true; setRecording(true); + setPaused(false); + setElapsedSeconds(0); window.electronAPI?.setRecordingState(true); const activeScreenRecorder = screenRecorder.current; @@ -536,7 +569,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { finalizeRecording( activeScreenRecorder, activeWebcamRecorder ?? null, - Math.max(0, Date.now() - startTime.current), + Math.max(0, getRecordingDurationMs()), activeRecordingId, ); }, @@ -552,12 +585,56 @@ export function useScreenRecorder(): UseScreenRecorderReturn { toast.error(errorMsg); } setRecording(false); + setPaused(false); + setElapsedSeconds(0); + accumulatedDurationMs.current = 0; + segmentStartedAt.current = null; screenRecorder.current = null; webcamRecorder.current = null; teardownMedia(); } }; + const togglePaused = () => { + const activeScreenRecorder = screenRecorder.current?.recorder; + if (!activeScreenRecorder || activeScreenRecorder.state === "inactive") { + return; + } + + const activeWebcamRecorder = webcamRecorder.current?.recorder; + + if (activeScreenRecorder.state === "paused") { + try { + activeScreenRecorder.resume(); + if (activeWebcamRecorder?.state === "paused") { + activeWebcamRecorder.resume(); + } + segmentStartedAt.current = Date.now(); + setPaused(false); + } catch (error) { + console.error("Failed to resume recording:", error); + } + return; + } + + if (activeScreenRecorder.state !== "recording") { + return; + } + + try { + accumulatedDurationMs.current = getRecordingDurationMs(); + segmentStartedAt.current = null; + setElapsedSeconds(Math.floor(accumulatedDurationMs.current / 1000)); + activeScreenRecorder.pause(); + if (activeWebcamRecorder?.state === "recording") { + activeWebcamRecorder.pause(); + } + setPaused(true); + } catch (error) { + console.error("Failed to pause recording:", error); + } + }; + const toggleRecording = () => { recording ? stopRecording.current() : startRecording(); }; @@ -566,7 +643,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (restarting.current) return; const activeScreenRecorder = screenRecorder.current; - if (!activeScreenRecorder || activeScreenRecorder.recorder.state !== "recording") return; + if (!activeScreenRecorder || activeScreenRecorder.recorder.state === "inactive") return; const activeWebcamRecorder = webcamRecorder.current; const activeRecordingId = recordingId.current; @@ -581,7 +658,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }), ]; - if (activeWebcamRecorder?.recorder.state === "recording") { + if ( + activeWebcamRecorder?.recorder.state === "recording" || + activeWebcamRecorder?.recorder.state === "paused" + ) { stopPromises.push( new Promise((resolve) => { activeWebcamRecorder.recorder.addEventListener("stop", () => resolve(), { @@ -601,9 +681,30 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } }; + useEffect(() => { + if (!recording) { + setElapsedSeconds(0); + return; + } + + setElapsedSeconds(Math.floor(getRecordingDurationMs() / 1000)); + if (paused) { + return; + } + + const interval = window.setInterval(() => { + setElapsedSeconds(Math.floor(getRecordingDurationMs() / 1000)); + }, 250); + + return () => window.clearInterval(interval); + }, [getRecordingDurationMs, paused, recording]); + return { recording, + paused, + elapsedSeconds, toggleRecording, + togglePaused, restartRecording, microphoneEnabled, setMicrophoneEnabled, From 3bfcd8576b43dcb5395d109feb135724cb6ca478 Mon Sep 17 00:00:00 2001 From: theaiagent Date: Fri, 3 Apr 2026 22:44:25 +0300 Subject: [PATCH 013/228] fix: read live video.currentTime for rapid frame steps and add JSDoc - Read currentTime directly from the video element instead of the React ref so rapid arrow key presses each advance by exactly one frame - Add JSDoc docstrings to frameStep.ts exports --- src/components/video-editor/VideoEditor.tsx | 10 +++++++--- src/lib/frameStep.ts | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 3efd9ce..e83f5b0 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -949,13 +949,17 @@ export default function VideoEditor() { return; } e.preventDefault(); + const video = videoPlaybackRef.current?.video; + if (!video) { + return; + } const direction = e.key === "ArrowLeft" ? "backward" : "forward"; const newTime = computeFrameStepTime( - currentTimeRef.current, - durationRef.current, + video.currentTime, + Number.isFinite(video.duration) ? video.duration : durationRef.current, direction, ); - handleSeek(newTime); + video.currentTime = newTime; return; } diff --git a/src/lib/frameStep.ts b/src/lib/frameStep.ts index 7eaaf6b..dc42d78 100644 --- a/src/lib/frameStep.ts +++ b/src/lib/frameStep.ts @@ -1,5 +1,10 @@ +/** Duration of a single frame in seconds at 60 FPS (~16.67ms). */ export const FRAME_DURATION_SEC = 1 / 60; +/** + * Compute the new playhead time after stepping one frame forward or backward. + * The result is clamped to the range [0, duration]. + */ export function computeFrameStepTime( currentTime: number, duration: number, From 97c9a73578ce547356593f03e258ce1595415549 Mon Sep 17 00:00:00 2001 From: theaiagent Date: Fri, 3 Apr 2026 23:02:12 +0300 Subject: [PATCH 014/228] fix: skip frame-step on ARIA widgets that own arrow keys Expand the arrow key guard to also skip elements with role="separator" (PanelResizeHandle), role="slider", and role="spinbutton" so keyboard panel resizing is not intercepted. --- src/components/video-editor/VideoEditor.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index e83f5b0..e3a30cf 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -944,7 +944,9 @@ export default function VideoEditor() { target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || - (target instanceof HTMLElement && target.isContentEditable) + (target instanceof HTMLElement && + (target.isContentEditable || + target.closest('[role="separator"], [role="slider"], [role="spinbutton"]'))) ) { return; } From f972556443ddbf5577fde31a5d6db93dfa188165 Mon Sep 17 00:00:00 2001 From: lueckpeter76-lgtm Date: Fri, 3 Apr 2026 18:33:54 -0600 Subject: [PATCH 015/228] 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 43ec6ee9cd74d921f5c12cfc43f0fa563862eb22 Mon Sep 17 00:00:00 2001 From: Ayush765-spec Date: Sat, 4 Apr 2026 11:51:05 +0530 Subject: [PATCH 016/228] fix(editor): localize new recording dialog and fix session clear behavior --- src/components/video-editor/VideoEditor.tsx | 24 +++++++++++---------- src/i18n/locales/en/editor.json | 6 ++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index dae009a..26bfd44 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -474,10 +474,14 @@ export default function VideoEditor() { }, [saveProject]); const handleNewRecordingConfirm = useCallback(async () => { - setShowNewRecordingDialog(false); - await window.electronAPI.clearCurrentVideoPath(); - await window.electronAPI.setCurrentRecordingSession(null); - await window.electronAPI.switchToHud(); + try { + await window.electronAPI.clearCurrentVideoPath(); + await window.electronAPI.switchToHud(); + setShowNewRecordingDialog(false); + } catch (err) { + console.error("Failed to start new recording:", err); + setError("Failed to start new recording: " + String(err)); + } }, []); const handleLoadProject = useCallback(async () => { @@ -1415,10 +1419,8 @@ export default function VideoEditor() { style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties} > - New Recording - - Start a new recording? Your current recording will be discarded. - + {t("newRecording.title")} + {t("newRecording.description")} @@ -1470,7 +1472,7 @@ export default function VideoEditor() { className="flex items-center gap-1 px-2 py-1 rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-all duration-150 text-[11px] font-medium" >
onPaddingChange?.(values[0])} onValueCommit={() => onPaddingCommit?.()} min={0} @@ -874,7 +963,9 @@ export function SettingsPanel({
- {t("background.title")} + + {t("background.title")} +
@@ -939,7 +1030,9 @@ export function SettingsPanel({ role="button" > ))} @@ -1265,7 +1392,9 @@ export function SettingsPanel({ {gifOutputDimensions.width} × {gifOutputDimensions.height}px
- {t("gifSettings.loop")} + + {t("gifSettings.loop")} + - {exportFormat === "gif" ? t("export.gifButton") : t("export.videoButton")} + {exportFormat === "gif" + ? t("export.gifButton") + : t("export.videoButton")}
@@ -1314,7 +1445,9 @@ export function SettingsPanel({ ))}
)} + {webcamLayoutPreset === "picture-in-picture" && ( +
+
+
+ {t("layout.webcamSize")} +
+
+ {webcamSizePreset}% +
+
+ onWebcamSizePresetChange?.(values[0])} + onValueCommit={() => onWebcamSizePresetCommit?.()} + min={10} + max={50} + step={1} + className="w-full" + /> +
+ )}
)} - +
- - {t("effects.title")} - + {t("effects.title")}
@@ -862,9 +815,7 @@ export function SettingsPanel({ {t("effects.motionBlur")} - {motionBlurAmount === 0 - ? t("effects.off") - : motionBlurAmount.toFixed(2)} + {motionBlurAmount === 0 ? t("effects.off") : motionBlurAmount.toFixed(2)} {t("effects.roundness")} - - {borderRadius}px - + {borderRadius}px - onBorderRadiusChange?.(values[0]) - } + onValueChange={(values) => onBorderRadiusChange?.(values[0])} onValueCommit={() => onBorderRadiusCommit?.()} min={0} max={16} @@ -925,15 +872,11 @@ export function SettingsPanel({ {t("effects.padding")} - {webcamLayoutPreset === "vertical-stack" - ? "—" - : `${padding}%`} + {webcamLayoutPreset === "vertical-stack" ? "—" : `${padding}%`} onPaddingChange?.(values[0])} onValueCommit={() => onPaddingCommit?.()} min={0} @@ -963,9 +906,7 @@ export function SettingsPanel({
- - {t("background.title")} - + {t("background.title")}
@@ -1030,9 +971,7 @@ export function SettingsPanel({ role="button" > ))} @@ -1392,9 +1299,7 @@ export function SettingsPanel({ {gifOutputDimensions.width} × {gifOutputDimensions.height}px
- - {t("gifSettings.loop")} - + {t("gifSettings.loop")} - {exportFormat === "gif" - ? t("export.gifButton") - : t("export.videoButton")} + {exportFormat === "gif" ? t("export.gifButton") : t("export.videoButton")}
@@ -1445,9 +1348,7 @@ export function SettingsPanel({ {recording && ( - + diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json index 1b772a2..ea2ceaa 100644 --- a/src/i18n/locales/en/editor.json +++ b/src/i18n/locales/en/editor.json @@ -1,7 +1,7 @@ { "newRecording": { - "title": "New Recording", - "description": "Start a new recording? Your current recording will be discarded.", + "title": "Return to Recorder", + "description": "Your current session has been saved.", "cancel": "Cancel", "confirm": "Confirm" }, diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index e3ed9b4..d76ee15 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -20,6 +20,7 @@ interface Window { getSources: (opts: Electron.SourcesOptions) => Promise; switchToEditor: () => Promise; switchToHud: () => Promise; + startNewRecording: () => Promise<{ success: boolean; error?: string }>; openSourceSelector: () => Promise; selectSource: (source: ProcessedDesktopSource) => Promise; getSelectedSource: () => Promise; From 475cbcd76c673211208f4747db9ed6c6f4b6613c Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sun, 5 Apr 2026 10:05:04 -0700 Subject: [PATCH 037/228] revert: undo manual merge of PR #314 --- package-lock.json | 4 +- src/components/launch/LaunchWindow.tsx | 53 ++++++----- src/hooks/useScreenRecorder.ts | 119 +++---------------------- src/i18n/locales/en/launch.json | 2 - src/i18n/locales/es/launch.json | 2 - src/i18n/locales/zh-CN/launch.json | 2 - 6 files changed, 38 insertions(+), 144 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdbd6b9..70e3395 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openscreen", - "version": "1.3.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openscreen", - "version": "1.3.0", + "version": "1.2.0", "dependencies": { "@fix-webm-duration/fix": "^1.0.1", "@pixi/filter-drop-shadow": "^5.2.0", diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 249dd77..d1185e8 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -1,6 +1,6 @@ import { ChevronDown, Languages } from "lucide-react"; import { useEffect, useState } from "react"; -import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs"; +import { BsRecordCircle } from "react-icons/bs"; import { FaRegStopCircle } from "react-icons/fa"; import { FaFolderOpen } from "react-icons/fa6"; import { FiMinus, FiX } from "react-icons/fi"; @@ -42,8 +42,6 @@ const ICON_CONFIG = { micOff: { icon: MdMicOff, size: ICON_SIZE }, webcamOn: { icon: MdVideocam, size: ICON_SIZE }, webcamOff: { icon: MdVideocamOff, size: ICON_SIZE }, - pause: { icon: BsPauseCircle, size: ICON_SIZE }, - resume: { icon: BsPlayCircle, size: ICON_SIZE }, stop: { icon: FaRegStopCircle, size: ICON_SIZE }, restart: { icon: MdRestartAlt, size: ICON_SIZE }, cancel: { icon: MdCancel, size: ICON_SIZE }, @@ -81,10 +79,7 @@ export function LaunchWindow() { const { recording, - paused, - elapsedSeconds, toggleRecording, - togglePaused, restartRecording, cancelRecording, microphoneEnabled, @@ -98,6 +93,8 @@ export function LaunchWindow() { webcamDeviceId, setWebcamDeviceId, } = useScreenRecorder(); + const [recordingStart, setRecordingStart] = useState(null); + const [elapsed, setElapsed] = useState(0); const showMicControls = microphoneEnabled && !recording; const showWebcamControls = webcamEnabled && !recording; @@ -152,6 +149,25 @@ export function LaunchWindow() { } }, [selectedCameraId, setWebcamDeviceId]); + useEffect(() => { + let timer: NodeJS.Timeout | null = null; + if (recording) { + if (!recordingStart) setRecordingStart(Date.now()); + timer = setInterval(() => { + if (recordingStart) { + setElapsed(Math.floor((Date.now() - recordingStart) / 1000)); + } + }, 1000); + } else { + setRecordingStart(null); + setElapsed(0); + if (timer) clearInterval(timer); + } + return () => { + if (timer) clearInterval(timer); + }; + }, [recording, recordingStart]); + useEffect(() => { if (!import.meta.env.DEV) { return; @@ -434,11 +450,7 @@ export function LaunchWindow() { {/* Record/Stop group */} - {recording && ( - - - - )} - {/* Restart recording */} {recording && ( diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index a676d66..25adf6d 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -41,10 +41,7 @@ const WEBCAM_TARGET_FRAME_RATE = 30; type UseScreenRecorderReturn = { recording: boolean; - paused: boolean; - elapsedSeconds: number; toggleRecording: () => void; - togglePaused: () => void; restartRecording: () => void; cancelRecording: () => void; microphoneEnabled: boolean; @@ -89,8 +86,6 @@ function createRecorderHandle(stream: MediaStream, options: MediaRecorderOptions export function useScreenRecorder(): UseScreenRecorderReturn { const t = useScopedT("editor"); const [recording, setRecording] = useState(false); - const [paused, setPaused] = useState(false); - const [elapsedSeconds, setElapsedSeconds] = useState(0); const [microphoneEnabled, setMicrophoneEnabled] = useState(false); const [microphoneDeviceId, setMicrophoneDeviceId] = useState(undefined); const [webcamDeviceId, setWebcamDeviceId] = useState(undefined); @@ -103,20 +98,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn { const microphoneStream = useRef(null); const webcamStream = useRef(null); const mixingContext = useRef(null); + const startTime = useRef(0); const recordingId = useRef(0); - const accumulatedDurationMs = useRef(0); - const segmentStartedAt = useRef(null); const finalizingRecordingId = useRef(null); const allowAutoFinalize = useRef(false); const discardRecordingId = useRef(null); const restarting = useRef(false); - const getRecordingDurationMs = useCallback(() => { - const segmentDuration = - segmentStartedAt.current === null ? 0 : Date.now() - segmentStartedAt.current; - return accumulatedDurationMs.current + segmentDuration; - }, []); - const selectMimeType = () => { const preferred = [ "video/webm;codecs=av1", @@ -215,10 +203,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { teardownMedia(); setRecording(false); - setPaused(false); - setElapsedSeconds(0); - accumulatedDurationMs.current = 0; - segmentStartedAt.current = null; window.electronAPI?.setRecordingState(false); void (async () => { @@ -290,7 +274,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } const activeWebcamRecorder = webcamRecorder.current; - const duration = getRecordingDurationMs(); + const duration = Date.now() - startTime.current; const activeRecordingId = recordingId.current; finalizeRecording( @@ -300,10 +284,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { activeRecordingId, ); - if ( - activeScreenRecorder.recorder.state === "recording" || - activeScreenRecorder.recorder.state === "paused" - ) { + if (activeScreenRecorder.recorder.state === "recording") { try { activeScreenRecorder.recorder.stop(); } catch { @@ -311,10 +292,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } } if (activeWebcamRecorder) { - if ( - activeWebcamRecorder.recorder.state === "recording" || - activeWebcamRecorder.recorder.state === "paused" - ) { + if (activeWebcamRecorder.recorder.state === "recording") { try { activeWebcamRecorder.recorder.stop(); } catch { @@ -339,20 +317,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn { restarting.current = false; discardRecordingId.current = null; - if ( - screenRecorder.current?.recorder.state === "recording" || - screenRecorder.current?.recorder.state === "paused" - ) { + if (screenRecorder.current?.recorder.state === "recording") { try { screenRecorder.current.recorder.stop(); } catch { // Ignore recorder teardown errors during cleanup. } } - if ( - webcamRecorder.current?.recorder.state === "recording" || - webcamRecorder.current?.recorder.state === "paused" - ) { + if (webcamRecorder.current?.recorder.state === "recording") { try { webcamRecorder.current.recorder.stop(); } catch { @@ -547,12 +519,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } recordingId.current = Date.now(); - accumulatedDurationMs.current = 0; - segmentStartedAt.current = Date.now(); + startTime.current = recordingId.current; allowAutoFinalize.current = true; setRecording(true); - setPaused(false); - setElapsedSeconds(0); window.electronAPI?.setRecordingState(true); const activeScreenRecorder = screenRecorder.current; @@ -568,7 +537,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { finalizeRecording( activeScreenRecorder, activeWebcamRecorder ?? null, - Math.max(0, getRecordingDurationMs()), + Math.max(0, Date.now() - startTime.current), activeRecordingId, ); }, @@ -584,56 +553,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn { toast.error(errorMsg); } setRecording(false); - setPaused(false); - setElapsedSeconds(0); - accumulatedDurationMs.current = 0; - segmentStartedAt.current = null; screenRecorder.current = null; webcamRecorder.current = null; teardownMedia(); } }; - const togglePaused = () => { - const activeScreenRecorder = screenRecorder.current?.recorder; - if (!activeScreenRecorder || activeScreenRecorder.state === "inactive") { - return; - } - - const activeWebcamRecorder = webcamRecorder.current?.recorder; - - if (activeScreenRecorder.state === "paused") { - try { - activeScreenRecorder.resume(); - if (activeWebcamRecorder?.state === "paused") { - activeWebcamRecorder.resume(); - } - segmentStartedAt.current = Date.now(); - setPaused(false); - } catch (error) { - console.error("Failed to resume recording:", error); - } - return; - } - - if (activeScreenRecorder.state !== "recording") { - return; - } - - try { - accumulatedDurationMs.current = getRecordingDurationMs(); - segmentStartedAt.current = null; - setElapsedSeconds(Math.floor(accumulatedDurationMs.current / 1000)); - activeScreenRecorder.pause(); - if (activeWebcamRecorder?.state === "recording") { - activeWebcamRecorder.pause(); - } - setPaused(true); - } catch (error) { - console.error("Failed to pause recording:", error); - } - }; - const toggleRecording = () => { recording ? stopRecording.current() : startRecording(); }; @@ -642,7 +567,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (restarting.current) return; const activeScreenRecorder = screenRecorder.current; - if (!activeScreenRecorder || activeScreenRecorder.recorder.state === "inactive") return; + if (!activeScreenRecorder || activeScreenRecorder.recorder.state !== "recording") return; const activeWebcamRecorder = webcamRecorder.current; const activeRecordingId = recordingId.current; @@ -657,10 +582,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }), ]; - if ( - activeWebcamRecorder?.recorder.state === "recording" || - activeWebcamRecorder?.recorder.state === "paused" - ) { + if (activeWebcamRecorder?.recorder.state === "recording") { stopPromises.push( new Promise((resolve) => { activeWebcamRecorder.recorder.addEventListener("stop", () => resolve(), { @@ -691,30 +613,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn { stopRecording.current(); }; - useEffect(() => { - if (!recording) { - setElapsedSeconds(0); - return; - } - - setElapsedSeconds(Math.floor(getRecordingDurationMs() / 1000)); - if (paused) { - return; - } - - const interval = window.setInterval(() => { - setElapsedSeconds(Math.floor(getRecordingDurationMs() / 1000)); - }, 250); - - return () => window.clearInterval(interval); - }, [getRecordingDurationMs, paused, recording]); - return { recording, - paused, - elapsedSeconds, toggleRecording, - togglePaused, restartRecording, cancelRecording, microphoneEnabled, diff --git a/src/i18n/locales/en/launch.json b/src/i18n/locales/en/launch.json index cf111c4..c1229cc 100644 --- a/src/i18n/locales/en/launch.json +++ b/src/i18n/locales/en/launch.json @@ -4,8 +4,6 @@ "closeApp": "Close App", "restartRecording": "Restart recording", "cancelRecording": "Cancel recording", - "pauseRecording": "Pause recording", - "resumeRecording": "Resume recording", "openVideoFile": "Open video file", "openProject": "Open project" }, diff --git a/src/i18n/locales/es/launch.json b/src/i18n/locales/es/launch.json index f47bc81..f5be07c 100644 --- a/src/i18n/locales/es/launch.json +++ b/src/i18n/locales/es/launch.json @@ -4,8 +4,6 @@ "closeApp": "Cerrar aplicación", "restartRecording": "Reiniciar grabación", "cancelRecording": "Cancelar grabación", - "pauseRecording": "Pausar grabación", - "resumeRecording": "Reanudar grabación", "openVideoFile": "Abrir archivo de video", "openProject": "Abrir proyecto" }, diff --git a/src/i18n/locales/zh-CN/launch.json b/src/i18n/locales/zh-CN/launch.json index 6b63df1..0c2b319 100644 --- a/src/i18n/locales/zh-CN/launch.json +++ b/src/i18n/locales/zh-CN/launch.json @@ -4,8 +4,6 @@ "closeApp": "关闭应用", "restartRecording": "重新开始录制", "cancelRecording": "取消录制", - "pauseRecording": "暂停录制", - "resumeRecording": "继续录制", "openVideoFile": "打开视频文件", "openProject": "打开项目" }, From c868469be57ef8abb961b15af55629d55fa4174c Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sun, 5 Apr 2026 10:17:35 -0700 Subject: [PATCH 038/228] fix: auto-finalize duration bug, restore cancelRecording, and add i18n for pause tooltips --- src/components/launch/LaunchWindow.tsx | 2 +- src/hooks/useScreenRecorder.ts | 15 ++++++++++++--- src/i18n/locales/en/launch.json | 2 ++ src/i18n/locales/es/launch.json | 2 ++ src/i18n/locales/zh-CN/launch.json | 2 ++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index 83306ee..249dd77 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -459,7 +459,7 @@ export function LaunchWindow() { {recording && ( - + + +
+
+ )} {/* 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 063/228] 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 064/228] 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 90ba71332395ca989c12f5dff4ac09db0b9ae35d Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Mon, 6 Apr 2026 15:08:49 +0530 Subject: [PATCH 065/228] fix(i18n): update tutorial dialog translation keys for all locales --- 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..d1fb685 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..6d7fe7d 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 4e2a53b2004f88f4847c501b8e016d752c96ab21 Mon Sep 17 00:00:00 2001 From: AmitwalaH Date: Mon, 6 Apr 2026 15:19:24 +0530 Subject: [PATCH 066/228] fix: spacing issues in tutorial translations --- src/i18n/locales/en/dialogs.json | 6 +++--- src/i18n/locales/es/dialogs.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/i18n/locales/en/dialogs.json b/src/i18n/locales/en/dialogs.json index d1fb685..a84b5fd 100644 --- a/src/i18n/locales/en/dialogs.json +++ b/src/i18n/locales/en/dialogs.json @@ -29,7 +29,7 @@ "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", + "explanationMiddle": " — anything", "covered": "covered", "explanationAfter": "by a red trim segment will be cut out when you export.", "visualExample": "Visual Example", @@ -40,8 +40,8 @@ "part3": "Part 3", "finalVideo": "Final Video", "step1Title": "1. Add Trim", - "step1DescriptionBefore": "Press", - "step1DescriptionAfter": "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 6d7fe7d..f8a5e63 100644 --- a/src/i18n/locales/es/dialogs.json +++ b/src/i18n/locales/es/dialogs.json @@ -29,7 +29,7 @@ "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", + "explanationMiddle": " — cualquier parte", "covered": "cubierta", "explanationAfter": "por un segmento rojo será eliminada al exportar.", "visualExample": "Ejemplo visual", @@ -40,8 +40,8 @@ "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.", + "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." }, From 7e563166a3633befba3fef6de149d7c8c18b16d7 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Mon, 6 Apr 2026 20:37:05 +0200 Subject: [PATCH 067/228] add color wheel to background and annotations --- package-lock.json | 137 ++++++++++++++ package.json | 1 + .../video-editor/AnnotationSettingsPanel.tsx | 168 +++++++++++++++--- src/components/video-editor/SettingsPanel.tsx | 105 +++++++++-- src/i18n/locales/en/settings.json | 2 + src/i18n/locales/es/settings.json | 2 + src/i18n/locales/zh-CN/settings.json | 2 + 7 files changed, 376 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdbd6b9..2ff6cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@types/gif.js": "^0.2.5", "@uiw/color-convert": "^2.9.2", "@uiw/react-color-block": "^2.9.2", + "@uiw/react-color-colorful": "^2.9.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dnd-timeline": "^2.2.0", @@ -4875,6 +4876,36 @@ "@babel/runtime": ">=7.19.0" } }, + "node_modules/@uiw/react-color-alpha": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-2.9.6.tgz", + "integrity": "sha512-DNzEVHZ0Izp4NAwzKqTcl4rLdPjSFjyZCP6Q2vKJEglugZ/bdPsmZaos9IYOrgnd1kPDmTSKZ/p8nI7vBIATGw==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-drag-event-interactive": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-alpha/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "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", @@ -4894,6 +4925,38 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-color-colorful": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-colorful/-/react-color-colorful-2.9.6.tgz", + "integrity": "sha512-h74zo+ve9Rpv7xwb1dRfoa23yN39b6eYScDIm7V2d5FzkXN6hR7jnnJ7ZUD9Joz/rdaCz1eFQD9ig+wp8+wSnQ==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-color-alpha": "2.9.6", + "@uiw/react-color-hue": "2.9.6", + "@uiw/react-color-saturation": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-colorful/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "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", @@ -4908,6 +4971,66 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-color-hue": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-2.9.6.tgz", + "integrity": "sha512-B99dW2/AHMD3py83BrXl94bhXeGCZR1FMpU/FNbIIbUrV9QTiIXDs2/SB/tMD9ltcSP59RD5Sc5m2vCb/8anjw==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-color-alpha": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-hue/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, + "node_modules/@uiw/react-color-saturation": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-2.9.6.tgz", + "integrity": "sha512-R1tiKbTG2WiJXerkmuaKnBFfzgyZUn08q9OjQSvNH1f3ov2/YeUVlOwQY9MbQE7ytZv+9x+1h0Lpk4QG7AdulQ==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-drag-event-interactive": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-saturation/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "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", @@ -4925,6 +5048,20 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-drag-event-interactive": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-2.9.6.tgz", + "integrity": "sha512-jXzt3Xis/BIYap2Hj2++gB3aEUD0mZoVNGfckurrwjAwxasxNiwkmTGxV5er3due0ZgaVKdOAfTRoYKlgZukSg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", diff --git a/package.json b/package.json index 8817372..2496471 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@types/gif.js": "^0.2.5", "@uiw/color-convert": "^2.9.2", "@uiw/react-color-block": "^2.9.2", + "@uiw/react-color-colorful": "^2.9.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dnd-timeline": "^2.2.0", diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index b289392..c897c03 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,4 +1,5 @@ import Block from "@uiw/react-color-block"; +import Colorful from "@uiw/react-color-colorful"; import { AlignCenter, AlignLeft, @@ -67,7 +68,7 @@ export function AnnotationSettingsPanel({ const t = useScopedT("settings"); const fileInputRef = useRef(null); const [customFonts, setCustomFonts] = useState([]); - + const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const fontStyleLabels: Record = { classic: t("fontStyles.classic"), editor: t("fontStyles.editor"), @@ -139,6 +140,15 @@ export function AnnotationSettingsPanel({ event.target.value = ""; }; + const getTextColor = (color: string) => { + if (color === "transparent") return "#ffffff"; + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + const luminance = 0.299 * r + 0.587 * g + 0.114 * b; + if (luminance > 186) return "#000000"; + return "#ffffff"; + }; return (
@@ -380,17 +390,68 @@ export function AnnotationSettingsPanel({ - - { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> + +
+ {colorMode === "palette" && ( + { + onStyleChange({ color: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + /> + )} + {colorMode === "wheel" && ( + <> +
+ + {annotation.style.color} + +
+ { + onStyleChange({ color: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + )} +
+ + +
+
@@ -419,21 +480,74 @@ export function AnnotationSettingsPanel({ - - { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> + +
+ {colorMode === "palette" && ( + { + onStyleChange({ backgroundColor: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + /> + )} + {colorMode === "wheel" && ( + <> +
+ + {annotation.style.backgroundColor} + +
+ { + onStyleChange({ backgroundColor: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + )} +
+ + +
+
+ +
+ {backgroundColorMode === "wheel" && ( + <> +
+ + {selectedColor} + +
+ { + setSelectedColor(color.hex); + onWallpaperChange(color.hex); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + { + setSelectedColor(e.target.value); + onWallpaperChange(e.target.value); + }} + /> + + )} + {backgroundColorMode === "palette" && ( + { + setSelectedColor(color.hex); + onWallpaperChange(color.hex); + }} + style={{ + width: "100%", + borderRadius: "8px", + }} + /> + )}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 632a569..da98aea 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -108,6 +108,8 @@ "background": "Background", "none": "None", "color": "Color", + "colorWheel": "Color Wheel", + "colorPalette": "Color Palette", "clearBackground": "Clear Background", "uploadImage": "Upload Image", "supportedFormats": "Supported formats: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 586e840..9af4632 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -108,6 +108,8 @@ "background": "Fondo", "none": "Ninguno", "color": "Color", + "colorWheel": "Rueda de colores", + "colorPalette": "Paleta de colores", "clearBackground": "Quitar fondo", "uploadImage": "Subir imagen", "supportedFormats": "Formatos compatibles: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index ab0d41b..a9aa32d 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -108,6 +108,8 @@ "background": "背景", "none": "无", "color": "颜色", + "colorWheel": "颜色轮", + "colorPalette": "颜色调色板", "clearBackground": "清除背景", "uploadImage": "上传图片", "supportedFormats": "支持的格式:JPG、PNG、GIF、WebP", From 2c10073d308550fbfd09d7767134c97c73f90513 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Mon, 6 Apr 2026 21:02:50 +0200 Subject: [PATCH 068/228] ai review changes --- src/components/video-editor/SettingsPanel.tsx | 4 ++-- src/i18n/locales/en/settings.json | 4 +++- src/i18n/locales/es/settings.json | 4 +++- src/i18n/locales/zh-CN/settings.json | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 7e4ff35..6df3574 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1014,7 +1014,7 @@ export function SettingsPanel({ }} > - {t("annotation.colorWheel")} + {t("background.colorWheel")}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index da98aea..0d18efd 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -41,7 +41,9 @@ "color": "Color", "gradient": "Gradient", "uploadCustom": "Upload Custom", - "gradientLabel": "Gradient {{index}}" + "gradientLabel": "Gradient {{index}}", + "colorWheel": "Color Wheel", + "colorPalette": "Color Palette" }, "crop": { "title": "Crop", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 9af4632..1eb6d46 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -41,7 +41,9 @@ "color": "Color", "gradient": "Degradado", "uploadCustom": "Subir personalizado", - "gradientLabel": "Degradado {{index}}" + "gradientLabel": "Degradado {{index}}", + "colorWheel": "Rueda de colores", + "colorPalette": "Paleta de colores" }, "crop": { "title": "Recortar", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index a9aa32d..8b554a4 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -41,7 +41,9 @@ "color": "颜色", "gradient": "渐变", "uploadCustom": "上传自定义", - "gradientLabel": "渐变 {{index}}" + "gradientLabel": "渐变 {{index}}", + "colorWheel": "颜色轮", + "colorPalette": "颜色调色板" }, "crop": { "title": "裁剪", From 112f02fe032de8a970cd9d022032bd959f5cbb9f Mon Sep 17 00:00:00 2001 From: moncef Date: Tue, 7 Apr 2026 00:30:23 +0100 Subject: [PATCH 069/228] 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 && ( + + + {colorMode === "wheel" && ( + <> +
+ {selectedColor} +
+ { + onUpdateColor(color.hex); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + + )} + {colorMode === "palette" && ( + { + onUpdateColor(color.hex); + }} + style={{ + width: "100%", + borderRadius: "8px", + }} + /> + )} + {clearBackgroundOption && ( + + )} + + ); +} diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index c897c03..eb6a9be 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,5 +1,4 @@ import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { AlignCenter, AlignLeft, @@ -31,6 +30,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useScopedT } from "@/contexts/I18nContext"; import { type CustomFont, getCustomFonts } from "@/lib/customFonts"; import { cn } from "@/lib/utils"; +import ColorPicker from "../ui/color-picker"; import { AddCustomFontDialog } from "./AddCustomFontDialog"; import { getArrowComponent } from "./ArrowSvgs"; import type { AnnotationRegion, AnnotationType, ArrowDirection, FigureData } from "./types"; @@ -68,7 +68,6 @@ export function AnnotationSettingsPanel({ const t = useScopedT("settings"); const fileInputRef = useRef(null); const [customFonts, setCustomFonts] = useState([]); - const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const fontStyleLabels: Record = { classic: t("fontStyles.classic"), editor: t("fontStyles.editor"), @@ -140,15 +139,6 @@ export function AnnotationSettingsPanel({ event.target.value = ""; }; - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; return (
@@ -394,64 +384,17 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.color} - -
- { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
+ { + onStyleChange({ color: color }); + }} + />
@@ -484,80 +427,19 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.backgroundColor} - -
- { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
- + clearBackgroundOption={true} + onUpdateColor={(color) => { + onStyleChange({ backgroundColor: color }); + }} + />
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 6df3574..05d4940 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,5 +1,3 @@ -import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { Bug, Crop, @@ -42,7 +40,7 @@ import { GIF_FRAME_RATES, GIF_SIZE_PRESETS } from "@/lib/exporter"; import { cn } from "@/lib/utils"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; -import { Input } from "../ui/input"; +import ColorPicker from "../ui/color-picker"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; @@ -229,7 +227,6 @@ export function SettingsPanel({ const t = useScopedT("settings"); const [wallpaperPaths, setWallpaperPaths] = useState([]); const [customImages, setCustomImages] = useState([]); - const [backgroundColorMode, setBackgroundColorMode] = useState<"wheel" | "palette">("wheel"); const fileInputRef = useRef(null); useEffect(() => { @@ -322,16 +319,6 @@ export function SettingsPanel({ [cropRegion, onCropChange, videoWidth, videoHeight, cropAspectLocked], ); - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; - const applyCropAspectPreset = useCallback( (preset: string) => { if (!cropRegion || !onCropChange) return; @@ -1001,84 +988,18 @@ export function SettingsPanel({ -
-
- - -
- {backgroundColorMode === "wheel" && ( - <> -
- - {selectedColor} - -
- { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - { - setSelectedColor(e.target.value); - onWallpaperChange(e.target.value); - }} - /> - - )} - {backgroundColorMode === "palette" && ( - { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - width: "100%", - borderRadius: "8px", - }} - /> - )} -
+ { + setSelectedColor(color); + onWallpaperChange(color); + }} + />
From 85bd215f1f386140d4cf4cffb410a7d658ca0b41 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 8 Apr 2026 13:58:53 +0800 Subject: [PATCH 085/228] fix(i18n): add missing zh-CN translation for newRecording dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zh-CN locale was missing the 'newRecording' section in editor.json, which is present in the en locale. This commit adds the translation for: - title: 返回录屏 - description: 当前会话已保存。 - cancel: 取消 - confirm: 确认 --- src/i18n/locales/zh-CN/editor.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json index 5d27bef..44abab9 100644 --- a/src/i18n/locales/zh-CN/editor.json +++ b/src/i18n/locales/zh-CN/editor.json @@ -1,4 +1,10 @@ { + "newRecording": { + "title": "返回录屏", + "description": "当前会话已保存。", + "cancel": "取消", + "confirm": "确认" + }, "errors": { "noVideoLoaded": "未加载视频", "videoNotReady": "视频未就绪", From 5494acb5bafa303bdc54d521d7cf913d07edfb08 Mon Sep 17 00:00:00 2001 From: Sid <70214527+siddharthvaddem@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:21:02 -0700 Subject: [PATCH 086/228] Merge pull request #365 from AmitwalaH/fix-tutorial-translations fix(i18n): add missing tutorial dialog translation keys --- 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 fdfeb51c00bc0a6a9dc76d9a5810291215c456a0 Mon Sep 17 00:00:00 2001 From: Aaryash Khalkar <91302334+imAaryash@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:19:08 +0530 Subject: [PATCH 087/228] Fix SUPPORTED_LOCALES array syntax --- src/i18n/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a4f1c6a2ee738dfbeeefffc779bc5203a0e26b70 Mon Sep 17 00:00:00 2001 From: Lorenzo Lancia Date: Wed, 8 Apr 2026 16:42:12 +0200 Subject: [PATCH 088/228] feat: add blur selection (rectangle, oval) --- package-lock.json | 1348 ----------------- .../video-editor/AnnotationOverlay.tsx | 256 +++- .../video-editor/AnnotationSettingsPanel.tsx | 7 +- .../video-editor/BlurSettingsPanel.tsx | 140 ++ src/components/video-editor/SettingsPanel.tsx | 26 + src/components/video-editor/VideoEditor.tsx | 148 +- src/components/video-editor/VideoPlayback.tsx | 95 +- .../video-editor/projectPersistence.ts | 40 +- .../video-editor/timeline/TimelineEditor.tsx | 107 +- src/components/video-editor/types.ts | 34 +- src/i18n/locales/en/settings.json | 5 + src/i18n/locales/en/timeline.json | 3 + src/i18n/locales/es/settings.json | 5 + src/i18n/locales/es/timeline.json | 9 +- src/i18n/locales/fr/settings.json | 5 + src/i18n/locales/fr/timeline.json | 19 +- src/i18n/locales/tr/settings.json | 5 + src/i18n/locales/tr/timeline.json | 9 +- src/i18n/locales/zh-CN/settings.json | 5 + src/i18n/locales/zh-CN/timeline.json | 9 +- src/lib/exporter/annotationRenderer.ts | 95 +- 21 files changed, 973 insertions(+), 1397 deletions(-) create mode 100644 src/components/video-editor/BlurSettingsPanel.tsx diff --git a/package-lock.json b/package-lock.json index 4f43a91..998f8f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4993,397 +4993,6 @@ } } }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser-playwright/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" - ], - "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", @@ -5410,549 +5019,6 @@ } } }, - "node_modules/@vitest/browser-playwright/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, - "optional": true, - "peer": 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/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, - "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, - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@vitest/browser-playwright/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==", - "dev": true, - "optional": true, - "peer": true, - "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" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@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.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.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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vitest/browser/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" - ], - "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", @@ -5979,82 +5045,6 @@ } } }, - "node_modules/@vitest/browser/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, - "optional": true, - "peer": 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/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, - "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, - "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", @@ -6076,82 +5066,6 @@ "node": ">=14.19.0" } }, - "node_modules/@vitest/browser/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==", - "dev": true, - "optional": true, - "peer": true, - "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" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@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.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", @@ -10718,268 +9632,6 @@ "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, - "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-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, - "optional": true, - "os": [ - "android" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "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, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "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, - "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", diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 11548c7..0f47fa7 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -1,8 +1,26 @@ -import { useRef } from "react"; +import { type CSSProperties, type PointerEvent, useRef, useState } from "react"; import { Rnd } from "react-rnd"; import { cn } from "@/lib/utils"; import { getArrowComponent } from "./ArrowSvgs"; -import type { AnnotationRegion } from "./types"; +import { + type AnnotationRegion, + type BlurData, + DEFAULT_BLUR_DATA, + DEFAULT_BLUR_INTENSITY, +} from "./types"; + +function buildBlurPolygonClipPath(points: Array<{ x: number; y: number }>) { + if (points.length < 3) return undefined; + const polygon = points.map((point) => `${point.x}% ${point.y}%`).join(", "); + return `polygon(${polygon})`; +} + +function buildBlurFreehandPath(points: Array<{ x: number; y: number }>, closed = true) { + if (closed ? points.length < 3 : points.length < 2) return null; + const [firstPoint, ...rest] = points; + const path = `M ${firstPoint.x} ${firstPoint.y} ${rest.map((point) => `L ${point.x} ${point.y}`).join(" ")}`; + return closed ? `${path} Z` : path; +} interface AnnotationOverlayProps { annotation: AnnotationRegion; @@ -11,6 +29,8 @@ interface AnnotationOverlayProps { containerHeight: number; onPositionChange: (id: string, position: { x: number; y: number }) => void; onSizeChange: (id: string, size: { width: number; height: number }) => void; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; onClick: (id: string) => void; zIndex: number; isSelectedBoost: boolean; // Boost z-index when selected for easy editing @@ -23,6 +43,8 @@ export function AnnotationOverlay({ containerHeight, onPositionChange, onSizeChange, + onBlurDataChange, + onBlurDataCommit, onClick, zIndex, isSelectedBoost, @@ -31,8 +53,16 @@ export function AnnotationOverlay({ const y = (annotation.position.y / 100) * containerHeight; const width = (annotation.size.width / 100) * containerWidth; const height = (annotation.size.height / 100) * containerHeight; + const isSelectedFreehandBlur = false; const isDraggingRef = useRef(false); + const isDrawingFreehandRef = useRef(false); + const freehandPointsRef = useRef>([]); + const [isFreehandDrawing, setIsFreehandDrawing] = useState(false); + const [draftFreehandPoints, setDraftFreehandPoints] = useState>( + [], + ); + const [livePointerPoint, setLivePointerPoint] = useState<{ x: number; y: number } | null>(null); const renderArrow = () => { const direction = annotation.figureData?.arrowDirection || "right"; @@ -43,6 +73,95 @@ export function AnnotationOverlay({ return ; }; + const normalizePoint = (event: PointerEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + const x = ((event.clientX - rect.left) / rect.width) * 100; + const y = ((event.clientY - rect.top) / rect.height) * 100; + return { + x: Math.max(0, Math.min(100, x)), + y: Math.max(0, Math.min(100, y)), + }; + }; + + const appendFreehandPoint = (point: { x: number; y: number }) => { + const points = freehandPointsRef.current; + const lastPoint = points[points.length - 1]; + if (!lastPoint) { + points.push(point); + return; + } + const dx = point.x - lastPoint.x; + const dy = point.y - lastPoint.y; + // Keep enough points to follow the cursor closely. + if (Math.hypot(dx, dy) >= 0.03) { + points.push(point); + } + }; + + const handleFreehandPointerDown = (event: PointerEvent) => { + if ( + !isSelected || + annotation.type !== "blur" || + annotation.blurData?.shape !== "freehand" || + !onBlurDataChange + ) { + return; + } + event.preventDefault(); + event.stopPropagation(); + event.currentTarget.setPointerCapture(event.pointerId); + isDrawingFreehandRef.current = true; + setIsFreehandDrawing(true); + const point = normalizePoint(event); + freehandPointsRef.current = [point]; + setDraftFreehandPoints([point]); + setLivePointerPoint(point); + }; + + const handleFreehandPointerMove = (event: PointerEvent) => { + if (!isDrawingFreehandRef.current) return; + event.preventDefault(); + event.stopPropagation(); + const point = normalizePoint(event); + setLivePointerPoint(point); + appendFreehandPoint(point); + setDraftFreehandPoints([...freehandPointsRef.current]); + }; + + const finishFreehandPointer = (event: PointerEvent) => { + if (!isDrawingFreehandRef.current || !onBlurDataChange) return; + isDrawingFreehandRef.current = false; + setIsFreehandDrawing(false); + try { + event.currentTarget.releasePointerCapture(event.pointerId); + } catch { + // no-op if already released + } + const points = [...freehandPointsRef.current]; + if (livePointerPoint) { + const last = points[points.length - 1]; + if (!last || Math.hypot(last.x - livePointerPoint.x, last.y - livePointerPoint.y) > 0.001) { + points.push(livePointerPoint); + } + } + if (points.length >= 3) { + const closedPoints = [...points]; + const first = closedPoints[0]; + const last = closedPoints[closedPoints.length - 1]; + if (Math.hypot(last.x - first.x, last.y - first.y) > 0.001) { + closedPoints.push({ ...first }); + } + onBlurDataChange(annotation.id, { + ...(annotation.blurData || { ...DEFAULT_BLUR_DATA, shape: "freehand" }), + shape: "freehand", + freehandPoints: closedPoints, + }); + setDraftFreehandPoints(closedPoints); + onBlurDataCommit?.(); + } + setLivePointerPoint(null); + }; + const renderContent = () => { switch (annotation.type) { case "text": @@ -113,6 +232,114 @@ export function AnnotationOverlay({
{renderArrow()}
); + case "blur": { + const shape = annotation.blurData?.shape === "oval" ? "oval" : "rectangle"; + const blurIntensity = Math.max( + 1, + Math.round(annotation.blurData?.intensity ?? DEFAULT_BLUR_INTENSITY), + ); + const activeFreehandPoints = + shape === "freehand" + ? isFreehandDrawing + ? draftFreehandPoints + : (annotation.blurData?.freehandPoints ?? []) + : []; + const drawingPoints = + isFreehandDrawing && livePointerPoint + ? (() => { + const last = activeFreehandPoints[activeFreehandPoints.length - 1]; + if (!last) return [livePointerPoint]; + const dx = livePointerPoint.x - last.x; + const dy = livePointerPoint.y - last.y; + return Math.hypot(dx, dy) > 0.01 + ? [...activeFreehandPoints, livePointerPoint] + : activeFreehandPoints; + })() + : activeFreehandPoints; + const clipPath = + shape === "freehand" ? buildBlurPolygonClipPath(activeFreehandPoints) : undefined; + const freehandPath = + shape === "freehand" + ? buildBlurFreehandPath( + isFreehandDrawing ? drawingPoints : activeFreehandPoints, + !isFreehandDrawing, + ) + : null; + const currentPointerPoint = isFreehandDrawing + ? livePointerPoint || drawingPoints[drawingPoints.length - 1] || null + : null; + const shapeBorderRadius = shape === "oval" ? "50%" : shape === "rectangle" ? "8px" : "0"; + const shouldShowFreehandBlurFill = + shape !== "freehand" || (!!clipPath && !isFreehandDrawing); + const shapeMaskStyle: CSSProperties = { + borderRadius: shapeBorderRadius, + clipPath: isFreehandDrawing ? undefined : clipPath, + WebkitClipPath: isFreehandDrawing ? undefined : clipPath, + }; + const isFreehandSelected = isSelected && shape === "freehand"; + return ( +
+
+
+ {isSelected && shape !== "freehand" && ( +
+ )} +
+ {isSelected && shape === "freehand" && freehandPath && ( + + + {currentPointerPoint && ( + + )} + + )} + {isFreehandSelected && ( +
+ )} +
+ ); + } + default: return null; } @@ -149,18 +376,23 @@ export function AnnotationOverlay({ }} bounds="parent" className={cn( - "cursor-move transition-all", - isSelected && "ring-2 ring-[#34B27B] ring-offset-2 ring-offset-transparent", + "cursor-move", + isSelected && + annotation.type !== "blur" && + "ring-2 ring-[#34B27B] ring-offset-2 ring-offset-transparent", )} style={{ zIndex: isSelectedBoost ? zIndex + 1000 : zIndex, // Boost selected annotation to ensure it's on top pointerEvents: isSelected ? "auto" : "none", - border: isSelected ? "2px solid rgba(52, 178, 123, 0.8)" : "none", - backgroundColor: isSelected ? "rgba(52, 178, 123, 0.1)" : "transparent", - boxShadow: isSelected ? "0 0 0 1px rgba(52, 178, 123, 0.35)" : "none", + border: + isSelected && annotation.type !== "blur" ? "2px solid rgba(52, 178, 123, 0.8)" : "none", + backgroundColor: + isSelected && annotation.type !== "blur" ? "rgba(52, 178, 123, 0.1)" : "transparent", + boxShadow: + isSelected && annotation.type !== "blur" ? "0 0 0 1px rgba(52, 178, 123, 0.35)" : "none", }} - enableResizing={isSelected} - disableDragging={!isSelected} + enableResizing={isSelected && !isSelectedFreehandBlur} + disableDragging={!isSelected || isSelectedFreehandBlur} resizeHandleStyles={{ topLeft: { width: "12px", @@ -206,11 +438,13 @@ export function AnnotationOverlay({ >
{renderContent()} diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index b289392..f5c2a0b 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -32,7 +32,12 @@ import { type CustomFont, getCustomFonts } from "@/lib/customFonts"; import { cn } from "@/lib/utils"; import { AddCustomFontDialog } from "./AddCustomFontDialog"; import { getArrowComponent } from "./ArrowSvgs"; -import type { AnnotationRegion, AnnotationType, ArrowDirection, FigureData } from "./types"; +import { + type AnnotationRegion, + type AnnotationType, + type ArrowDirection, + type FigureData, +} from "./types"; interface AnnotationSettingsPanelProps { annotation: AnnotationRegion; diff --git a/src/components/video-editor/BlurSettingsPanel.tsx b/src/components/video-editor/BlurSettingsPanel.tsx new file mode 100644 index 0000000..88a8f3a --- /dev/null +++ b/src/components/video-editor/BlurSettingsPanel.tsx @@ -0,0 +1,140 @@ +import { Info, Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Slider } from "@/components/ui/slider"; +import { useScopedT } from "@/contexts/I18nContext"; +import { cn } from "@/lib/utils"; +import { + type AnnotationRegion, + type BlurData, + type BlurShape, + DEFAULT_BLUR_DATA, + MAX_BLUR_INTENSITY, + MIN_BLUR_INTENSITY, +} from "./types"; + +interface BlurSettingsPanelProps { + blurRegion: AnnotationRegion; + onBlurDataChange: (blurData: BlurData) => void; + onBlurDataCommit?: () => void; + onDelete: () => void; +} + +export function BlurSettingsPanel({ + blurRegion, + onBlurDataChange, + onBlurDataCommit, + onDelete, +}: BlurSettingsPanelProps) { + const t = useScopedT("settings"); + + const blurShapeOptions: Array<{ value: BlurShape; labelKey: string }> = [ + { value: "rectangle", labelKey: "blurShapeRectangle" }, + { value: "oval", labelKey: "blurShapeOval" }, + ]; + + return ( +
+
+
+ {t("annotation.blurShape")} + + {t("annotation.active")} + +
+ +
+ {blurShapeOptions.map((shape) => { + const activeShape = blurRegion.blurData?.shape || DEFAULT_BLUR_DATA.shape; + const isActive = activeShape === shape.value; + 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} + /> + )); + })()}
)}
From 38d72217c22500c2c6fd8924aa2dd5b4bb71cb28 Mon Sep 17 00:00:00 2001 From: LorenzoLancia Date: Wed, 8 Apr 2026 22:43:30 +0200 Subject: [PATCH 094/228] fix little things blur --- src/components/video-editor/VideoEditor.tsx | 17 +++++++++++++---- src/components/video-editor/timeline/Item.tsx | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index c162730..ffe3add 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -1028,8 +1028,17 @@ export default function VideoEditor() { return updatedRegion; }), })); + + if (type === "blur" && selectedAnnotationId === id) { + setSelectedAnnotationId(null); + setSelectedBlurId(id); + setSelectedSpeedId(null); + } else if (type !== "blur" && selectedBlurId === id) { + setSelectedBlurId(null); + setSelectedAnnotationId(id); + } }, - [pushState], + [pushState, selectedAnnotationId, selectedBlurId], ); const handleAnnotationStyleChange = useCallback( @@ -1212,14 +1221,14 @@ export default function VideoEditor() { useEffect(() => { if ( selectedAnnotationId && - !annotationRegions.some((region) => region.id === selectedAnnotationId) + !annotationOnlyRegions.some((region) => region.id === selectedAnnotationId) ) { setSelectedAnnotationId(null); } - if (selectedBlurId && !annotationRegions.some((region) => region.id === selectedBlurId)) { + if (selectedBlurId && !blurRegions.some((region) => region.id === selectedBlurId)) { setSelectedBlurId(null); } - }, [selectedAnnotationId, selectedBlurId, annotationRegions]); + }, [selectedAnnotationId, selectedBlurId, annotationOnlyRegions, blurRegions]); useEffect(() => { if (selectedSpeedId && !speedRegions.some((region) => region.id === selectedSpeedId)) { diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index f265fe4..d2d0298 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -14,7 +14,7 @@ interface ItemProps { onSelect?: () => void; zoomDepth?: number; speedValue?: number; - variant?: "zoom" | "trim" | "annotation" | "speed"; + variant?: "zoom" | "trim" | "annotation" | "speed" | "blur"; } // Map zoom depth to multiplier labels From c3faca19fd7f6dfcbe06b61c5c1965b1431ca21b Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 22:45:27 +0200 Subject: [PATCH 095/228] small fix: color block handles transparent values --- src/components/ui/color-picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx index a1b78d5..d8ec2b3 100644 --- a/src/components/ui/color-picker.tsx +++ b/src/components/ui/color-picker.tsx @@ -131,7 +131,7 @@ export default function ColorPicker(props: ColorPickerProps) { )} {colorMode === "palette" && ( { onUpdateColor(color.hex); From 283fa406b259097f1b95c908b727dfe80ad57fa6 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 23:00:33 +0200 Subject: [PATCH 096/228] langages : tr and fr --- src/i18n/locales/fr/settings.json | 6 +++++- src/i18n/locales/tr/settings.json | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index dd7610f..48ce7e3 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -41,7 +41,9 @@ "color": "Couleur", "gradient": "Dégradé", "uploadCustom": "Téléverser une image", - "gradientLabel": "Dégradé {{index}}" + "gradientLabel": "Dégradé {{index}}", + "colorWheel": "Roue chromatique", + "colorPalette": "Palette de couleurs" }, "crop": { "title": "Recadrage", @@ -108,6 +110,8 @@ "background": "Arrière-plan", "none": "Aucun", "color": "Couleur", + "colorWheel": "Roue chromatique", + "colorPalette": "Palette de couleurs", "clearBackground": "Supprimer l'arrière-plan", "uploadImage": "Téléverser une image", "supportedFormats": "Formats supportés : JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json index 1fa4668..3cf33b1 100644 --- a/src/i18n/locales/tr/settings.json +++ b/src/i18n/locales/tr/settings.json @@ -41,7 +41,9 @@ "color": "Renk", "gradient": "Gradyan", "uploadCustom": "Özel Yükle", - "gradientLabel": "Gradyan {{index}}" + "gradientLabel": "Gradyan {{index}}", + "colorWheel": "Renk çarkı", + "colorPalette": "Renk paleti" }, "crop": { "title": "Kırpma", @@ -108,6 +110,8 @@ "background": "Arka Plan", "none": "Yok", "color": "Renk", + "colorWheel": "Renk çarkı", + "colorPalette": "Renk paleti", "clearBackground": "Arka Planı Temizle", "uploadImage": "Görüntü Yükle", "supportedFormats": "Desteklenen biçimler: JPG, PNG, GIF, WebP", From cf6dce552efb479071d5d4428018b423d73e6c83 Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 9 Apr 2026 16:58:12 +0800 Subject: [PATCH 097/228] Fix security and reliability issues 1. Validate URL scheme in open-external-url handler - Prevent opening file:// or other dangerous schemes via shell.openExternal - Only allow http:, https:, and mailto: protocols 2. Fix latest video detection using mtime instead of lexicographic sort - Lexicographic sort gives wrong results (e.g. recording-9 > recording-10) - Now sorts by file modification time for reliable latest-file detection 3. Add null guard for AudioData.format in cloneWithTimestamp - Replace non-null assertion (!) with proper validation - Throws descriptive error if format is unexpectedly null 4. Prevent encodeQueue counter underflow in VideoExporter - Use Math.max(0, ...) to prevent negative queue count Co-Authored-By: Claude Opus 4.6 --- electron/ipc/handlers.ts | 31 ++++++++++++++++++++++++++++++- src/lib/exporter/audioEncoder.ts | 7 +++++-- src/lib/exporter/videoExporter.ts | 2 +- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 4cb4875..6aae4b4 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -490,7 +490,24 @@ export function registerIpcHandlers( return { success: false, message: "No recorded video found" }; } - const latestVideo = videoFiles.sort().reverse()[0]; + // Sort by most recently modified to reliably get the latest recording. + // Lexicographic sort is unreliable (e.g. recording-9.webm > recording-10.webm). + let latestVideo: string | null = null; + let latestMtimeMs = 0; + for (const file of videoFiles) { + try { + const stat = await fs.stat(path.join(RECORDINGS_DIR, file)); + if (stat.mtimeMs > latestMtimeMs) { + latestMtimeMs = stat.mtimeMs; + latestVideo = file; + } + } catch { + // Skip inaccessible files. + } + } + if (!latestVideo) { + return { success: false, message: "No recorded video found" }; + } const videoPath = path.join(RECORDINGS_DIR, latestVideo); return { success: true, path: videoPath }; @@ -616,6 +633,18 @@ export function registerIpcHandlers( ipcMain.handle("open-external-url", async (_, url: string) => { try { + const ALLOWED_SCHEMES = ["http:", "https:", "mailto:"]; + let parsed: URL; + try { + parsed = new URL(url); + } catch { + return { success: false, error: "Invalid URL" }; + } + + if (!ALLOWED_SCHEMES.includes(parsed.protocol)) { + return { success: false, error: `Unsupported URL scheme: ${parsed.protocol}` }; + } + await shell.openExternal(url); return { success: true }; } catch (error) { diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index 490eed2..fba3568 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -459,7 +459,10 @@ export class AudioProcessor { } private cloneWithTimestamp(src: AudioData, newTimestamp: number): AudioData { - const isPlanar = src.format?.includes("planar") ?? false; + if (!src.format) { + throw new Error("AudioData format is required for cloning"); + } + const isPlanar = src.format.includes("planar"); const numPlanes = isPlanar ? src.numberOfChannels : 1; let totalSize = 0; @@ -476,7 +479,7 @@ export class AudioProcessor { } return new AudioData({ - format: src.format!, + format: src.format, sampleRate: src.sampleRate, numberOfFrames: src.numberOfFrames, numberOfChannels: src.numberOfChannels, diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index d0affd1..2c6ea8d 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -422,7 +422,7 @@ export class VideoExporter { })(); this.muxingPromises.push(muxingPromise); - this.encodeQueue--; + this.encodeQueue = Math.max(0, this.encodeQueue - 1); }, error: (error) => { console.error("[VideoExporter] Encoder error:", error); From 32329181976c7d7ac6ab1647f8075939ddfb5563 Mon Sep 17 00:00:00 2001 From: LorenzoLancia Date: Thu, 9 Apr 2026 21:51:27 +0200 Subject: [PATCH 098/228] Add the Shortcut Blur --- src/i18n/locales/en/timeline.json | 4 ++-- src/i18n/locales/es/timeline.json | 4 ++-- src/i18n/locales/fr/timeline.json | 4 ++-- src/i18n/locales/tr/timeline.json | 4 ++-- src/i18n/locales/zh-CN/timeline.json | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/i18n/locales/en/timeline.json b/src/i18n/locales/en/timeline.json index 775b2ec..b4d5bd8 100644 --- a/src/i18n/locales/en/timeline.json +++ b/src/i18n/locales/en/timeline.json @@ -4,14 +4,14 @@ "suggestZooms": "Suggest Zooms from Cursor", "addTrim": "Add Trim (T)", "addAnnotation": "Add Annotation (A)", - "addBlur": "Add Blur", + "addBlur": "Add Blur (B)", "addSpeed": "Add Speed (S)" }, "hints": { "pressZoom": "Press Z to add zoom", "pressTrim": "Press T to add trim", "pressAnnotation": "Press A to add annotation", - "pressBlur": "Add blur regions from toolbar", + "pressBlur": "Press B to add blur region", "pressSpeed": "Press S to add speed" }, "labels": { diff --git a/src/i18n/locales/es/timeline.json b/src/i18n/locales/es/timeline.json index 512bce0..12a83b0 100644 --- a/src/i18n/locales/es/timeline.json +++ b/src/i18n/locales/es/timeline.json @@ -5,14 +5,14 @@ "addTrim": "Agregar recorte (T)", "addAnnotation": "Agregar anotación (A)", "addSpeed": "Agregar velocidad (S)", - "addBlur": "Agregar desenfoque" + "addBlur": "Agregar desenfoque (B)" }, "hints": { "pressZoom": "Presiona Z para agregar zoom", "pressTrim": "Presiona T para agregar recorte", "pressAnnotation": "Presiona A para agregar anotación", "pressSpeed": "Presiona S para agregar velocidad", - "pressBlur": "Agrega regiones de desenfoque desde la barra" + "pressBlur": "Presiona B para agregar una región de desenfoque" }, "labels": { "pan": "Desplazar", diff --git a/src/i18n/locales/fr/timeline.json b/src/i18n/locales/fr/timeline.json index 30e57df..5985ea6 100644 --- a/src/i18n/locales/fr/timeline.json +++ b/src/i18n/locales/fr/timeline.json @@ -5,14 +5,14 @@ "addTrim": "Ajouter une coupe (T)", "addAnnotation": "Ajouter une annotation (A)", "addSpeed": "Ajouter une vitesse (S)", - "addBlur": "Ajouter un flou" + "addBlur": "Ajouter un flou (B)" }, "hints": { "pressZoom": "Appuyez sur Z pour ajouter un zoom", "pressTrim": "Appuyez sur T pour ajouter une coupe", "pressAnnotation": "Appuyez sur A pour ajouter une annotation", "pressSpeed": "Appuyez sur S pour ajouter une vitesse", - "pressBlur": "Ajoutez des zones de flou depuis la barre" + "pressBlur": "Appuyez sur B pour ajouter une zone de flou" }, "labels": { "pan": "Panoramique", diff --git a/src/i18n/locales/tr/timeline.json b/src/i18n/locales/tr/timeline.json index 46aa2c5..294640b 100644 --- a/src/i18n/locales/tr/timeline.json +++ b/src/i18n/locales/tr/timeline.json @@ -5,14 +5,14 @@ "addTrim": "Kırpma Ekle (T)", "addAnnotation": "Açıklama Ekle (A)", "addSpeed": "Hız Ekle (S)", - "addBlur": "Bulanık ekle" + "addBlur": "Bulanık ekle (B)" }, "hints": { "pressZoom": "Yakınlaştırma eklemek için Z tuşuna basın", "pressTrim": "Kırpma eklemek için T tuşuna basın", "pressAnnotation": "Açıklama eklemek için A tuşuna basın", "pressSpeed": "Hız eklemek için S tuşuna basın", - "pressBlur": "Araçtan bulanık bölgeleri ekleyin" + "pressBlur": "Bulanık bölge eklemek için B tuşuna basın" }, "labels": { "pan": "Kaydır", diff --git a/src/i18n/locales/zh-CN/timeline.json b/src/i18n/locales/zh-CN/timeline.json index 13b3ecd..7841dcb 100644 --- a/src/i18n/locales/zh-CN/timeline.json +++ b/src/i18n/locales/zh-CN/timeline.json @@ -5,14 +5,14 @@ "addTrim": "添加剪辑 (T)", "addAnnotation": "添加标注 (A)", "addSpeed": "添加速度 (S)", - "addBlur": "添加模糊" + "addBlur": "添加模糊 (B)" }, "hints": { "pressZoom": "按 Z 添加缩放", "pressTrim": "按 T 添加剪辑", "pressAnnotation": "按 A 添加标注", "pressSpeed": "按 S 添加速度", - "pressBlur": "从工具栏添加模糊区域" + "pressBlur": "按 B 添加模糊区域" }, "labels": { "pan": "平移", From d512f59826d6106edd70142698b4764d5819bce6 Mon Sep 17 00:00:00 2001 From: kwakseongjae Date: Fri, 10 Apr 2026 16:00:25 +0900 Subject: [PATCH 099/228] feat(i18n): add Korean (ko-KR) localization - Add complete Korean locale across all 7 i18n namespaces - All translation keys match the English baseline 1:1 - Register ko-KR in SUPPORTED_LOCALES and i18n-check validation Refs siddharthvaddem/openscreen#406 --- scripts/i18n-check.mjs | 2 +- src/i18n/config.ts | 2 +- src/i18n/locales/ko-KR/common.json | 29 +++++ src/i18n/locales/ko-KR/dialogs.json | 70 +++++++++++ src/i18n/locales/ko-KR/editor.json | 41 +++++++ src/i18n/locales/ko-KR/launch.json | 37 ++++++ src/i18n/locales/ko-KR/settings.json | 162 ++++++++++++++++++++++++++ src/i18n/locales/ko-KR/shortcuts.json | 36 ++++++ src/i18n/locales/ko-KR/timeline.json | 50 ++++++++ 9 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 src/i18n/locales/ko-KR/common.json create mode 100644 src/i18n/locales/ko-KR/dialogs.json create mode 100644 src/i18n/locales/ko-KR/editor.json create mode 100644 src/i18n/locales/ko-KR/launch.json create mode 100644 src/i18n/locales/ko-KR/settings.json create mode 100644 src/i18n/locales/ko-KR/shortcuts.json create mode 100644 src/i18n/locales/ko-KR/timeline.json diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index c320946..ca73b23 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"]; +const COMPARE_LOCALES = ["zh-CN", "es", "tr", "ko-KR"]; function getKeys(obj, prefix = "") { const keys = []; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 636d727..0933569 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", "ko-KR"] as const; export const I18N_NAMESPACES = [ "common", "dialogs", diff --git a/src/i18n/locales/ko-KR/common.json b/src/i18n/locales/ko-KR/common.json new file mode 100644 index 0000000..b83cb44 --- /dev/null +++ b/src/i18n/locales/ko-KR/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": "KO" + } +} diff --git a/src/i18n/locales/ko-KR/dialogs.json b/src/i18n/locales/ko-KR/dialogs.json new file mode 100644 index 0000000..3093cdf --- /dev/null +++ b/src/i18n/locales/ko-KR/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/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json new file mode 100644 index 0000000..4db7d1f --- /dev/null +++ b/src/i18n/locales/ko-KR/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/ko-KR/launch.json b/src/i18n/locales/ko-KR/launch.json new file mode 100644 index 0000000..1cc695e --- /dev/null +++ b/src/i18n/locales/ko-KR/launch.json @@ -0,0 +1,37 @@ +{ + "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": "언어" +} diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json new file mode 100644 index 0000000..cd9f734 --- /dev/null +++ b/src/i18n/locales/ko-KR/settings.json @@ -0,0 +1,162 @@ +{ + "zoom": { + "level": "줌 레벨", + "selectRegion": "조정할 줌 구간을 선택하세요", + "deleteZoom": "줌 삭제", + "focusMode": { + "title": "포커스 모드", + "manual": "수동", + "auto": "자동", + "autoDescription": "녹화된 커서 위치를 따라 카메라가 이동합니다" + } + }, + "speed": { + "playbackSpeed": "재생 속도", + "selectRegion": "조정할 속도 구간을 선택하세요", + "deleteRegion": "속도 구간 삭제", + "customPlaybackSpeed": "재생 속도 직접 입력", + "maxSpeedError": "속도는 16×를 초과할 수 없습니다" + }, + "trim": { + "deleteRegion": "트림 구간 삭제" + }, + "layout": { + "title": "레이아웃", + "preset": "프리셋", + "selectPreset": "프리셋 선택", + "pictureInPicture": "화면 속 화면", + "verticalStack": "세로 배치", + "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에 Star 남기기" + }, + "imageUpload": { + "invalidFileType": "지원하지 않는 파일 형식입니다", + "jpgOnly": "JPG 또는 JPEG 이미지 파일을 업로드해 주세요.", + "uploadSuccess": "커스텀 이미지가 성공적으로 업로드되었습니다!", + "failedToUpload": "이미지 업로드에 실패했습니다", + "errorReading": "파일을 읽는 중 오류가 발생했습니다." + }, + "annotation": { + "title": "주석 설정", + "active": "활성", + "typeText": "텍스트", + "typeImage": "이미지", + "typeArrow": "화살표", + "textContent": "텍스트 내용", + "textPlaceholder": "텍스트를 입력하세요...", + "fontStyle": "폰트 스타일", + "selectStyle": "스타일 선택", + "size": "크기", + "customFonts": "커스텀 폰트", + "textColor": "텍스트 색상", + "background": "배경", + "none": "없음", + "color": "색상", + "clearBackground": "배경 지우기", + "uploadImage": "이미지 업로드", + "supportedFormats": "지원 형식: JPG, PNG, GIF, WebP", + "arrowDirection": "화살표 방향", + "strokeWidth": "선 두께: {{width}}px", + "arrowColor": "화살표 색상", + "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에서 폰트 선택 → \"폰트 가져오기\" 클릭 → @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/ko-KR/shortcuts.json b/src/i18n/locales/ko-KR/shortcuts.json new file mode 100644 index 0000000..1760d8f --- /dev/null +++ b/src/i18n/locales/ko-KR/shortcuts.json @@ -0,0 +1,36 @@ +{ + "title": "키보드 단축키", + "customize": "사용자 지정", + "configurable": "변경 가능", + "fixed": "고정", + "pressKey": "키를 누르세요...", + "clickToChange": "클릭해서 변경", + "pressEscToCancel": "Esc를 눌러 취소", + "helpText": "단축키를 클릭한 후 새 키 조합을 누르세요. 취소하려면 Esc를 누르세요.", + "resetToDefaults": "기본값으로 초기화", + "alreadyUsedBy": "이미 {{action}}에서 사용 중입니다", + "swap": "교체", + "reservedShortcut": "이 단축키는 \"{{label}}\"에 예약되어 있어 변경할 수 없습니다.", + "savedToast": "키보드 단축키가 저장되었습니다", + "resetToast": "기본 단축키로 초기화되었습니다 — 저장을 클릭해 적용하세요", + "actions": { + "addZoom": "줌 추가", + "addTrim": "트림 추가", + "addSpeed": "속도 추가", + "addAnnotation": "주석 추가", + "addKeyframe": "키프레임 추가", + "deleteSelected": "선택 항목 삭제", + "playPause": "재생 / 일시정지" + }, + "fixedActions": { + "undo": "실행 취소", + "redo": "다시 실행", + "cycleAnnotationsForward": "주석 앞으로 순환", + "cycleAnnotationsBackward": "주석 뒤로 순환", + "deleteSelectedAlt": "선택 항목 삭제 (대체)", + "panTimeline": "타임라인 이동", + "zoomTimeline": "타임라인 확대/축소", + "frameBack": "이전 프레임", + "frameForward": "다음 프레임" + } +} diff --git a/src/i18n/locales/ko-KR/timeline.json b/src/i18n/locales/ko-KR/timeline.json new file mode 100644 index 0000000..167c26f --- /dev/null +++ b/src/i18n/locales/ko-KR/timeline.json @@ -0,0 +1,50 @@ +{ + "buttons": { + "addZoom": "줌 추가 (Z)", + "suggestZooms": "커서 기반 줌 제안", + "addTrim": "트림 추가 (T)", + "addAnnotation": "주석 추가 (A)", + "addSpeed": "속도 추가 (S)" + }, + "hints": { + "pressZoom": "Z를 눌러 줌 추가", + "pressTrim": "T를 눌러 트림 추가", + "pressAnnotation": "A를 눌러 주석 추가", + "pressSpeed": "S를 눌러 속도 추가" + }, + "labels": { + "pan": "이동", + "zoom": "줌", + "zoomItem": "줌 {{index}}", + "trimItem": "트림 {{index}}", + "speedItem": "속도 {{index}}", + "annotationItem": "주석", + "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}}개가 추가되었습니다" + } +} From e7d82e147863d90ae0f0744aa3b1ea2d8ecd79b6 Mon Sep 17 00:00:00 2001 From: Scott Lexium Date: Fri, 10 Apr 2026 12:09:37 +0100 Subject: [PATCH 100/228] fix: make HUD overlay and source selector follow across macOS Spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both windows had alwaysOnTop but lacked setVisibleOnAllWorkspaces, so they stayed pinned to the Space they were first opened on. Users moving to a different virtual desktop would lose sight of the overlay. Calls setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) on macOS only — no-op on Windows/Linux so cross-platform behaviour is unchanged. --- electron/windows.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/electron/windows.ts b/electron/windows.ts index fb9a655..35a28b3 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -51,6 +51,12 @@ export function createHudOverlayWindow(): BrowserWindow { }, }); + // Follow the user across macOS Spaces (virtual desktops). + // Without this the HUD stays pinned to the Space it was first opened on. + if (process.platform === "darwin") { + win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + } + win.webContents.on("did-finish-load", () => { win?.webContents.send("main-process-message", new Date().toLocaleString()); }); @@ -142,6 +148,12 @@ export function createSourceSelectorWindow(): BrowserWindow { }, }); + // Follow the user across macOS Spaces so the selector appears on the + // active desktop regardless of where the HUD was originally opened. + if (process.platform === "darwin") { + win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + } + if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + "?windowType=source-selector"); } else { From 0bde3594211a34fd91180bf9d632e80346c23a2f Mon Sep 17 00:00:00 2001 From: Scott Lexium Date: Fri, 10 Apr 2026 12:28:47 +0100 Subject: [PATCH 101/228] docs: add JSDoc comments to window factory functions --- electron/windows.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/electron/windows.ts b/electron/windows.ts index 35a28b3..dcd9f92 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -17,6 +17,11 @@ ipcMain.on("hud-overlay-hide", () => { } }); +/** + * Creates the always-on-top HUD overlay window centred at the bottom of the + * primary display. The window is frameless, transparent, and follows the user + * across macOS Spaces so it is never lost when switching virtual desktops. + */ export function createHudOverlayWindow(): BrowserWindow { const primaryDisplay = screen.getPrimaryDisplay(); const { workArea } = primaryDisplay; @@ -80,6 +85,10 @@ export function createHudOverlayWindow(): BrowserWindow { return win; } +/** + * Creates the main editor window. Starts maximised with a hidden title bar on + * macOS. This window is not always-on-top and appears in the taskbar/dock. + */ export function createEditorWindow(): BrowserWindow { const isMac = process.platform === "darwin"; @@ -126,6 +135,10 @@ export function createEditorWindow(): BrowserWindow { return win; } +/** + * Creates the floating source-selector window used to pick a screen or window + * to record. Frameless, transparent, and follows the user across macOS Spaces. + */ export function createSourceSelectorWindow(): BrowserWindow { const { width, height } = screen.getPrimaryDisplay().workAreaSize; From d21dd1cbf1e4861e296fe88f66601ac74c3afa15 Mon Sep 17 00:00:00 2001 From: Theodor Peifer Date: Fri, 10 Apr 2026 22:24:37 +0200 Subject: [PATCH 102/228] fix: export frame counter exceeding total frames --- src/lib/exporter/gifExporter.ts | 4 ++-- src/lib/exporter/streamingDecoder.ts | 19 ++++++++++++++++--- src/lib/exporter/videoExporter.ts | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 747e34e..58ed693 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -174,11 +174,11 @@ export class GifExporter { }); // Calculate effective duration and frame count (excluding trim regions) - const effectiveDuration = this.streamingDecoder.getEffectiveDuration( + const { effectiveDuration, totalFrames } = this.streamingDecoder.getExportMetrics( + this.config.frameRate, this.config.trimRegions, this.config.speedRegions, ); - const totalFrames = Math.ceil(effectiveDuration * this.config.frameRate); // Calculate frame delay in milliseconds (gif.js uses ms) const frameDelay = Math.round(1000 / this.config.frameRate); diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index ee67576..c028832 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -536,11 +536,24 @@ export class StreamingVideoDecoder { return segments; } - getEffectiveDuration(trimRegions?: TrimRegion[], speedRegions?: SpeedRegion[]): number { + getExportMetrics( + targetFrameRate: number, + trimRegions?: TrimRegion[], + speedRegions?: SpeedRegion[], + ): { effectiveDuration: number; totalFrames: number } { if (!this.metadata) throw new Error("Must call loadMetadata() first"); const trimSegments = this.computeSegments(this.metadata.duration, trimRegions); - const speedSegments = this.splitBySpeed(trimSegments, speedRegions); - return speedSegments.reduce((sum, seg) => sum + (seg.endSec - seg.startSec) / seg.speed, 0); + const segments = this.splitBySpeed(trimSegments, speedRegions); + return { + effectiveDuration: segments.reduce( + (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, + ), + }; } private splitBySpeed( diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index d0affd1..dcfcc3e 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -157,11 +157,11 @@ export class VideoExporter { this.muxer = muxer; await muxer.initialize(); - const effectiveDuration = streamingDecoder.getEffectiveDuration( + const { effectiveDuration, totalFrames } = streamingDecoder.getExportMetrics( + this.config.frameRate, this.config.trimRegions, this.config.speedRegions, ); - const totalFrames = Math.ceil(effectiveDuration * this.config.frameRate); const readEndSec = Math.max(videoInfo.duration, videoInfo.streamDuration ?? 0) + 0.5; console.log("[VideoExporter] Original duration:", videoInfo.duration, "s"); From 33a60fed8cac585789651d1254cb345792ed1ace Mon Sep 17 00:00:00 2001 From: Orchard Date: Sat, 11 Apr 2026 10:39:55 +0800 Subject: [PATCH 103/228] fix(tray): adjust icon size for macOS platform compatibility --- electron/main.ts | 11 ++++++----- icons/icons/mac/icon.icns | Bin 1422500 -> 1335122 bytes 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 0f06f9e..2f2d971 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -62,10 +62,11 @@ let mainWindow: BrowserWindow | null = null; let sourceSelectorWindow: BrowserWindow | null = null; let tray: Tray | null = null; let selectedSourceName = ""; +const isMac = process.platform === "darwin"; // Tray Icons -const defaultTrayIcon = getTrayIcon("openscreen.png"); -const recordingTrayIcon = getTrayIcon("rec-button.png"); +const defaultTrayIcon = getTrayIcon("openscreen.png", isMac ? 18 : 24); +const recordingTrayIcon = getTrayIcon("rec-button.png", isMac ? 18 : 24); function createWindow() { mainWindow = createHudOverlayWindow(); @@ -199,12 +200,12 @@ function createTray() { }); } -function getTrayIcon(filename: string) { +function getTrayIcon(filename: string, size: number) { return nativeImage .createFromPath(path.join(process.env.VITE_PUBLIC || RENDERER_DIST, filename)) .resize({ - width: 24, - height: 24, + width: size, + height: size, quality: "best", }); } diff --git a/icons/icons/mac/icon.icns b/icons/icons/mac/icon.icns index 7d5a493cb0d81aed6155d1ebd51799db7c4352da..02de106cff4af6b3f6e844b22c0a623fc8a77330 100644 GIT binary patch literal 1335122 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≈wJpZDt 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

BhX$2LG@%?w< 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[G=-ywYf{)T#Lx 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

W?hBv~jR5Pk#wd0}j$L(Z?;?kRT4DI*4VT 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%0Ae|j?+sg!h75(CWbwfGz+voKdwtk|_D{gOS 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�OsyD`D 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

h5U~4EA`1e~Cpy86$*Le&#;gCV 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

ASA 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

thLxU7|CfFqkokJA*h;H`vf{`d4x8*o{vU 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Μ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

)} - {/* 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 120/228] 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 121/228] 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 122/228] 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 123/228] 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 124/228] 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 125/228] 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 126/228] 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 127/228] 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 128/228] 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 129/228] 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 130/228] 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 131/228] 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 132/228] 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 133/228] 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 134/228] 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 135/228] 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 136/228] 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 137/228] 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 138/228] 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 139/228] 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 140/228] 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 141/228] 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 142/228] 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 143/228] 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 144/228] 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 145/228] 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 146/228] 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 147/228] 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 148/228] 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 149/228] 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 150/228] 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 151/228] 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 152/228] 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 153/228] 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 154/228] 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 155/228] 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 156/228] 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 157/228] 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 158/228] 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 159/228] 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 160/228] 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 161/228] 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 162/228] 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 163/228] 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 164/228] 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 165/228] 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 166/228] 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 167/228] 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 168/228] 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 169/228] 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 170/228] 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 171/228] 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 172/228] 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 173/228] 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 174/228] 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 175/228] 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 176/228] 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 177/228] 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 178/228] 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 179/228] 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 180/228] 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 181/228] 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 182/228] 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 183/228] 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 184/228] 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 185/228] 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 186/228] 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 187/228] 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 188/228] 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 189/228] =?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 190/228] 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 191/228] 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 192/228] 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 193/228] 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 194/228] 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 195/228] 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 196/228] 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 197/228] 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 198/228] 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 199/228] 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 200/228] 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 201/228] 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 202/228] 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 203/228] 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 204/228] 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 205/228] 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 206/228] 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 207/228] 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 208/228] 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 209/228] 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 210/228] 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 211/228] 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 212/228] 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 213/228] 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 214/228] 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 215/228] 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 216/228] 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 217/228] 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 218/228] 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 219/228] 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 220/228] 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 221/228] 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 222/228] 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 From 8e8b194454e48f7216e3fed21cf4882bd5d373df Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Thu, 30 Apr 2026 22:22:46 +0200 Subject: [PATCH 223/228] adds support for japanese and chineese (taiwan) --- src/i18n/locales/ja-JP/settings.json | 6 +++++- src/i18n/locales/ko-KR/settings.json | 6 +++++- src/i18n/locales/zh-TW/settings.json | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json index 9cad3ef..129217c 100644 --- a/src/i18n/locales/ja-JP/settings.json +++ b/src/i18n/locales/ja-JP/settings.json @@ -52,7 +52,9 @@ "color": "色", "gradient": "グラデーション", "uploadCustom": "カスタムをアップロード", - "gradientLabel": "グラデーション {{index}}" + "gradientLabel": "グラデーション {{index}}", + "colorWheel": "カラーホイール", + "colorPalette": "カラーパレット" }, "crop": { "title": "クロップ", @@ -120,6 +122,8 @@ "background": "背景", "none": "なし", "color": "色", + "colorWheel": "カラーホイール", + "colorPalette": "カラーパレット", "clearBackground": "背景をクリア", "uploadImage": "画像をアップロード", "supportedFormats": "サポートされている形式: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json index cd9f734..5defbb6 100644 --- a/src/i18n/locales/ko-KR/settings.json +++ b/src/i18n/locales/ko-KR/settings.json @@ -44,7 +44,9 @@ "color": "색상", "gradient": "그라디언트", "uploadCustom": "직접 업로드", - "gradientLabel": "그라디언트 {{index}}" + "gradientLabel": "그라디언트 {{index}}", + "colorWheel": "색상 휠", + "colorPalette": "색상 팔레트" }, "crop": { "title": "자르기", @@ -111,6 +113,8 @@ "background": "배경", "none": "없음", "color": "색상", + "colorWheel": "색상 휠", + "colorPalette": "색상 팔레트", "clearBackground": "배경 지우기", "uploadImage": "이미지 업로드", "supportedFormats": "지원 형식: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json index 6344a99..652ab5a 100644 --- a/src/i18n/locales/zh-TW/settings.json +++ b/src/i18n/locales/zh-TW/settings.json @@ -52,7 +52,9 @@ "color": "顏色", "gradient": "漸層", "uploadCustom": "上傳自訂", - "gradientLabel": "漸層 {{index}}" + "gradientLabel": "漸層 {{index}}", + "colorWheel": "色輪", + "colorPalette": "調色盤" }, "crop": { "title": "裁剪", @@ -120,6 +122,8 @@ "background": "背景", "none": "無", "color": "顏色", + "colorWheel": "色輪", + "colorPalette": "調色盤", "clearBackground": "清除背景", "uploadImage": "上傳圖片", "supportedFormats": "支援的格式:JPG、PNG、GIF、WebP", From d59db3d8392ba6de30fcfaa817881beee7d11079 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 2 May 2026 17:34:47 -0700 Subject: [PATCH 224/228] fix missing spanish locale --- src/i18n/locales/es/editor.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json index c71368a..8f6ad13 100644 --- a/src/i18n/locales/es/editor.json +++ b/src/i18n/locales/es/editor.json @@ -34,5 +34,12 @@ "cameraDisconnected": "Cámara web desconectada.", "cameraNotFound": "Cámara no encontrada.", "permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla." + }, + "loadingVideo": "Cargando video...", + "newRecording": { + "title": "Volver a la grabadora", + "description": "Tu sesión actual ha sido guardada.", + "cancel": "Cancelar", + "confirm": "Confirmar" } } From 0f28cc0f3813863f9ce7b610080de86a755402eb Mon Sep 17 00:00:00 2001 From: Siddharth Date: Sat, 2 May 2026 17:44:56 -0700 Subject: [PATCH 225/228] fix missing locales --- .../video-editor/ShortcutsConfigDialog.tsx | 162 +++++++++--------- src/i18n/locales/fr/editor.json | 3 +- src/i18n/locales/ja-JP/editor.json | 7 +- src/i18n/locales/ko-KR/editor.json | 4 +- src/i18n/locales/ko-KR/launch.json | 8 +- src/i18n/locales/ko-KR/settings.json | 18 +- src/i18n/locales/ko-KR/shortcuts.json | 3 +- src/i18n/locales/ko-KR/timeline.json | 9 +- src/i18n/locales/tr/editor.json | 11 +- src/i18n/locales/tr/launch.json | 8 +- src/i18n/locales/tr/settings.json | 17 +- src/i18n/locales/zh-CN/settings.json | 9 +- src/i18n/locales/zh-TW/editor.json | 4 +- src/i18n/locales/zh-TW/launch.json | 8 +- src/i18n/locales/zh-TW/settings.json | 9 +- 15 files changed, 181 insertions(+), 99 deletions(-) diff --git a/src/components/video-editor/ShortcutsConfigDialog.tsx b/src/components/video-editor/ShortcutsConfigDialog.tsx index faa7513..6b9ef78 100644 --- a/src/components/video-editor/ShortcutsConfigDialog.tsx +++ b/src/components/video-editor/ShortcutsConfigDialog.tsx @@ -126,95 +126,99 @@ export function ShortcutsConfigDialog() { if (!open) handleClose(); }} > - - + + {t("title")} -
-

- {t("configurable")} -

- {SHORTCUT_ACTIONS.map((action) => { - const isCapturing = captureFor === action; - const hasConflict = conflict?.forAction === action; - return ( -
-
- {t(`actions.${action}`)} - -
- {hasConflict && conflict?.conflictWith.type === "configurable" && ( -
- - ⚠{" "} - {t("alreadyUsedBy", { action: t(`actions.${conflict.conflictWith.action}`) })} - -
- - -
+
+
+

+ {t("configurable")} +

+ {SHORTCUT_ACTIONS.map((action) => { + const isCapturing = captureFor === action; + const hasConflict = conflict?.forAction === action; + return ( +
+
+ {t(`actions.${action}`)} +
- )} + {hasConflict && conflict?.conflictWith.type === "configurable" && ( +
+ + ⚠{" "} + {t("alreadyUsedBy", { + action: t(`actions.${conflict.conflictWith.action}`), + })} + +
+ + +
+
+ )} +
+ ); + })} +
+ +
+

+ {t("fixed")} +

+ {FIXED_SHORTCUTS.map(({ i18nKey, label, display }) => ( +
+ + {t(`fixedActions.${i18nKey}`, { defaultValue: label })} + + + {display} +
- ); - })} + ))} +
+ +

{t("helpText")}

-
-

- {t("fixed")} -

- {FIXED_SHORTCUTS.map(({ i18nKey, label, display }) => ( -
- - {t(`fixedActions.${i18nKey}`, { defaultValue: label })} - - - {display} - -
- ))} -
- -

{t("helpText")}

- - +
+ {cursorHighlight && onCursorHighlightChange && ( +
+
+
Cursor highlight
+ +
+
+ {(["dot", "ring"] as const).map((style) => ( + + ))} +
+
+
+
Size
+ + {cursorHighlight.sizePx}px + +
+ + onCursorHighlightChange({ ...cursorHighlight, sizePx: values[0] }) + } + min={10} + max={36} + step={1} + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + /> +
+ {cursorHighlightSupportsClicks && ( +
+
Only on clicks
+ +
+ )} +
+
Color
+ + + + + + + onCursorHighlightChange({ ...cursorHighlight, color }) + } + /> + + +
+
+
+
Offset X (window recordings)
+ + {(cursorHighlight.offsetXNorm * 100).toFixed(1)}% + +
+ + onCursorHighlightChange({ + ...cursorHighlight, + offsetXNorm: values[0], + }) + } + min={-0.25} + max={0.25} + step={0.005} + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + /> +
+
+
+
Offset Y
+ + {(cursorHighlight.offsetYNorm * 100).toFixed(1)}% + +
+ + onCursorHighlightChange({ + ...cursorHighlight, + offsetYNorm: values[0], + }) + } + min={-0.25} + max={0.25} + step={0.005} + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + /> +
+
+ )} +
@@ -1957,6 +1978,9 @@ export default function VideoEditor() { {/* Right section: settings panel */}
pushState({ cursorHighlight: next })} + cursorHighlightSupportsClicks={isMac} selected={wallpaper} onWallpaperChange={(w) => pushState({ wallpaper: w })} selectedZoomDepth={ diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 35e0077..a69c8d7 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -51,7 +51,17 @@ import { ZOOM_SCALE_DEADZONE, ZOOM_TRANSLATION_DEADZONE_PX, } from "./videoPlayback/constants"; -import { adaptiveSmoothFactor, smoothCursorFocus } from "./videoPlayback/cursorFollowUtils"; +import { + adaptiveSmoothFactor, + interpolateCursorAt, + smoothCursorFocus, +} from "./videoPlayback/cursorFollowUtils"; +import { + type CursorHighlightConfig, + clickEmphasisAlpha, + DEFAULT_CURSOR_HIGHLIGHT, + drawCursorHighlightGraphics, +} from "./videoPlayback/cursorHighlight"; import { clampFocusToStage as clampFocusToStageUtil } from "./videoPlayback/focusUtils"; import { layoutVideoContent as layoutVideoContentUtil } from "./videoPlayback/layoutUtils"; import { clamp01 } from "./videoPlayback/mathUtils"; @@ -110,6 +120,8 @@ interface VideoPlaybackProps { onBlurDataChange?: (id: string, blurData: BlurData) => void; onBlurDataCommit?: () => void; cursorTelemetry?: import("./types").CursorTelemetryPoint[]; + cursorHighlight?: CursorHighlightConfig; + cursorClickTimestamps?: number[]; } export interface VideoPlaybackRef { @@ -168,6 +180,8 @@ const VideoPlayback = forwardRef( onBlurDataChange, onBlurDataCommit, cursorTelemetry = [], + cursorHighlight = DEFAULT_CURSOR_HIGHLIGHT, + cursorClickTimestamps = [], }, ref, ) => { @@ -191,6 +205,9 @@ const VideoPlayback = forwardRef( const currentTimeRef = useRef(0); const zoomRegionsRef = useRef([]); const cursorTelemetryRef = useRef([]); + const cursorHighlightRef = useRef(DEFAULT_CURSOR_HIGHLIGHT); + const cursorClickTimestampsRef = useRef([]); + const cursorHighlightGraphicsRef = useRef(null); const selectedZoomIdRef = useRef(null); const animationStateRef = useRef({ scale: 1, @@ -515,6 +532,17 @@ const VideoPlayback = forwardRef( cursorTelemetryRef.current = cursorTelemetry; }, [cursorTelemetry]); + useEffect(() => { + cursorHighlightRef.current = cursorHighlight; + if (cursorHighlightGraphicsRef.current) { + drawCursorHighlightGraphics(cursorHighlightGraphicsRef.current, cursorHighlight); + } + }, [cursorHighlight]); + + useEffect(() => { + cursorClickTimestampsRef.current = cursorClickTimestamps; + }, [cursorClickTimestamps]); + useEffect(() => { selectedZoomIdRef.current = selectedZoomId; }, [selectedZoomId]); @@ -738,6 +766,12 @@ const VideoPlayback = forwardRef( videoContainer.mask = maskGraphics; maskGraphicsRef.current = maskGraphics; + const cursorHighlightGraphics = new Graphics(); + cursorHighlightGraphics.visible = false; + videoContainer.addChild(cursorHighlightGraphics); + cursorHighlightGraphicsRef.current = cursorHighlightGraphics; + drawCursorHighlightGraphics(cursorHighlightGraphics, cursorHighlightRef.current); + animationStateRef.current = { scale: 1, focusX: DEFAULT_FOCUS.cx, @@ -797,6 +831,11 @@ const VideoPlayback = forwardRef( videoContainer.removeChild(maskGraphics); maskGraphics.destroy(); } + if (cursorHighlightGraphicsRef.current) { + videoContainer.removeChild(cursorHighlightGraphicsRef.current); + cursorHighlightGraphicsRef.current.destroy(); + cursorHighlightGraphicsRef.current = null; + } videoContainer.mask = null; maskGraphicsRef.current = null; if (blurFilterRef.current) { @@ -1016,6 +1055,39 @@ const VideoPlayback = forwardRef( motionVector, ); + const cursorGraphics = cursorHighlightGraphicsRef.current; + const cursorConfig = cursorHighlightRef.current; + const lockedDims = lockedVideoDimensionsRef.current; + if (cursorGraphics) { + if (cursorConfig.enabled && lockedDims && cursorTelemetryRef.current.length > 0) { + const emphasisAlpha = clickEmphasisAlpha( + currentTimeRef.current, + cursorClickTimestampsRef.current, + cursorConfig, + ); + const cursorPoint = + emphasisAlpha > 0 + ? interpolateCursorAt(cursorTelemetryRef.current, currentTimeRef.current) + : null; + if (cursorPoint) { + const baseScale = baseScaleRef.current; + const baseOffset = baseOffsetRef.current; + const cx = cursorPoint.cx + cursorConfig.offsetXNorm; + const cy = cursorPoint.cy + cursorConfig.offsetYNorm; + cursorGraphics.position.set( + baseOffset.x + cx * lockedDims.width * baseScale, + baseOffset.y + cy * lockedDims.height * baseScale, + ); + cursorGraphics.alpha = emphasisAlpha; + cursorGraphics.visible = true; + } else { + cursorGraphics.visible = false; + } + } else { + cursorGraphics.visible = false; + } + } + const isMotionBlurActive = (motionBlurAmountRef.current || 0) > 0 && isPlayingRef.current; if (isMotionBlurActive !== lastMotionBlurActive && videoContainerRef.current) { diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 7259c1e..beabbe4 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -80,6 +80,7 @@ export interface ProjectEditorState { gifFrameRate: GifFrameRate; gifLoop: boolean; gifSizePreset: GifSizePreset; + cursorHighlight: import("./videoPlayback/cursorHighlight").CursorHighlightConfig; } export interface EditorProjectData { @@ -494,6 +495,52 @@ export function normalizeProjectEditor(editor: Partial): Pro editor.gifSizePreset === "original" ? editor.gifSizePreset : "medium", + cursorHighlight: normalizeCursorHighlight(editor.cursorHighlight), + }; +} + +function normalizeCursorHighlight( + value: unknown, +): import("./videoPlayback/cursorHighlight").CursorHighlightConfig { + const fallback: import("./videoPlayback/cursorHighlight").CursorHighlightConfig = { + enabled: false, + style: "ring", + sizePx: 24, + color: "#FFD700", + opacity: 0.9, + onlyOnClicks: false, + clickEmphasisDurationMs: 350, + offsetXNorm: 0, + offsetYNorm: 0, + }; + if (!value || typeof value !== "object") return fallback; + const v = value as Partial; + return { + enabled: typeof v.enabled === "boolean" ? v.enabled : fallback.enabled, + style: v.style === "dot" || v.style === "ring" ? v.style : fallback.style, + sizePx: + typeof v.sizePx === "number" && v.sizePx >= 10 && v.sizePx <= 36 ? v.sizePx : fallback.sizePx, + color: + typeof v.color === "string" && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(v.color) + ? v.color + : fallback.color, + opacity: + typeof v.opacity === "number" && v.opacity >= 0 && v.opacity <= 1 + ? v.opacity + : fallback.opacity, + onlyOnClicks: typeof v.onlyOnClicks === "boolean" ? v.onlyOnClicks : fallback.onlyOnClicks, + clickEmphasisDurationMs: + typeof v.clickEmphasisDurationMs === "number" && v.clickEmphasisDurationMs > 0 + ? v.clickEmphasisDurationMs + : fallback.clickEmphasisDurationMs, + offsetXNorm: + typeof v.offsetXNorm === "number" && Number.isFinite(v.offsetXNorm) + ? Math.max(-1, Math.min(1, v.offsetXNorm)) + : fallback.offsetXNorm, + offsetYNorm: + typeof v.offsetYNorm === "number" && Number.isFinite(v.offsetYNorm) + ? Math.max(-1, Math.min(1, v.offsetYNorm)) + : fallback.offsetYNorm, }; } diff --git a/src/components/video-editor/videoPlayback/cursorHighlight.ts b/src/components/video-editor/videoPlayback/cursorHighlight.ts new file mode 100644 index 0000000..273e7b2 --- /dev/null +++ b/src/components/video-editor/videoPlayback/cursorHighlight.ts @@ -0,0 +1,125 @@ +import type { Graphics } from "pixi.js"; + +export type CursorHighlightStyle = "dot" | "ring"; + +export interface CursorHighlightConfig { + enabled: boolean; + style: CursorHighlightStyle; + sizePx: number; + color: string; + opacity: number; + // Show only on clicks (macOS — depends on click telemetry from uiohook). + onlyOnClicks: boolean; + clickEmphasisDurationMs: number; + // Per-recording manual nudge. Cursor telemetry is normalized to the display, + // but window recordings frame a subset of the display so the highlight + // lands offset. Users dial these in once to align with the actual cursor. + offsetXNorm: number; + offsetYNorm: number; +} + +export const CURSOR_HIGHLIGHT_MIN_SIZE_PX = 10; +export const CURSOR_HIGHLIGHT_MAX_SIZE_PX = 36; + +export const DEFAULT_CURSOR_HIGHLIGHT: CursorHighlightConfig = { + enabled: false, + style: "ring", + sizePx: 24, + color: "#FFD700", + opacity: 0.9, + onlyOnClicks: false, + clickEmphasisDurationMs: 350, + offsetXNorm: 0, + offsetYNorm: 0, +}; + +export const CURSOR_HIGHLIGHT_OFFSET_RANGE = 0.25; // ±25% of recorded surface + +// Alpha multiplier for the highlight at `timeMs`. Returns 1 when not in +// click-only mode; in click-only mode fades 1→0 across each click's window. +export function clickEmphasisAlpha( + timeMs: number, + clickTimestampsMs: number[] | undefined, + config: CursorHighlightConfig, +): number { + if (!config.onlyOnClicks) return 1; + if (!clickTimestampsMs || clickTimestampsMs.length === 0) return 0; + const window = Math.max(1, config.clickEmphasisDurationMs); + for (let i = 0; i < clickTimestampsMs.length; i++) { + const dt = timeMs - clickTimestampsMs[i]; + if (dt >= 0 && dt <= window) { + return 1 - dt / window; + } + } + return 0; +} + +function parseHexColor(hex: string): number { + const cleaned = hex.replace("#", ""); + if (cleaned.length === 3) { + const r = cleaned[0]; + const g = cleaned[1]; + const b = cleaned[2]; + return Number.parseInt(`${r}${r}${g}${g}${b}${b}`, 16); + } + return Number.parseInt(cleaned.slice(0, 6), 16); +} + +export function drawCursorHighlightGraphics(g: Graphics, config: CursorHighlightConfig): void { + g.clear(); + if (!config.enabled) return; + + const color = parseHexColor(config.color); + const radius = Math.max(1, config.sizePx / 2); + const alpha = Math.max(0, Math.min(1, config.opacity)); + + switch (config.style) { + case "dot": { + g.circle(0, 0, radius); + g.fill({ color, alpha }); + break; + } + case "ring": { + g.circle(0, 0, radius); + g.stroke({ color, alpha, width: Math.max(2, radius * 0.18) }); + break; + } + } +} + +export function drawCursorHighlightCanvas( + ctx: CanvasRenderingContext2D, + cx: number, + cy: number, + config: CursorHighlightConfig, + pixelScale = 1, +): void { + if (!config.enabled) return; + + const radius = Math.max(1, (config.sizePx / 2) * pixelScale); + const alpha = Math.max(0, Math.min(1, config.opacity)); + const color = config.color; + + ctx.save(); + ctx.globalAlpha = alpha; + + switch (config.style) { + case "dot": { + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(cx, cy, radius, 0, Math.PI * 2); + ctx.fill(); + break; + } + case "ring": { + ctx.beginPath(); + ctx.arc(cx, cy, radius, 0, Math.PI * 2); + ctx.strokeStyle = color; + ctx.lineWidth = Math.max(2, radius * 0.18); + ctx.stroke(); + break; + } + } + + ctx.restore(); +} diff --git a/src/hooks/useEditorHistory.ts b/src/hooks/useEditorHistory.ts index bd410da..a655137 100644 --- a/src/hooks/useEditorHistory.ts +++ b/src/hooks/useEditorHistory.ts @@ -17,6 +17,10 @@ import { DEFAULT_WEBCAM_POSITION, DEFAULT_WEBCAM_SIZE_PRESET, } from "@/components/video-editor/types"; +import { + type CursorHighlightConfig, + DEFAULT_CURSOR_HIGHLIGHT, +} from "@/components/video-editor/videoPlayback/cursorHighlight"; import { DEFAULT_WALLPAPER } from "@/lib/wallpaper"; import type { AspectRatio } from "@/utils/aspectRatioUtils"; @@ -39,6 +43,7 @@ export interface EditorState { webcamMaskShape: WebcamMaskShape; webcamSizePreset: WebcamSizePreset; webcamPosition: WebcamPosition | null; + cursorHighlight: CursorHighlightConfig; } export const INITIAL_EDITOR_STATE: EditorState = { @@ -58,6 +63,7 @@ export const INITIAL_EDITOR_STATE: EditorState = { webcamMaskShape: DEFAULT_WEBCAM_MASK_SHAPE, webcamSizePreset: DEFAULT_WEBCAM_SIZE_PRESET, webcamPosition: DEFAULT_WEBCAM_POSITION, + cursorHighlight: DEFAULT_CURSOR_HIGHLIGHT, }; type StateUpdate = Partial | ((prev: EditorState) => Partial); diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index ad65a08..0a151b0 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -28,8 +28,14 @@ import { } from "@/components/video-editor/videoPlayback/constants"; import { adaptiveSmoothFactor, + interpolateCursorAt, smoothCursorFocus, } from "@/components/video-editor/videoPlayback/cursorFollowUtils"; +import { + type CursorHighlightConfig, + clickEmphasisAlpha, + drawCursorHighlightCanvas, +} from "@/components/video-editor/videoPlayback/cursorHighlight"; import { clampFocusToStage as clampFocusToStageUtil } from "@/components/video-editor/videoPlayback/focusUtils"; import { findDominantRegion } from "@/components/video-editor/videoPlayback/zoomRegionUtils"; import { @@ -79,6 +85,8 @@ interface FrameRenderConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; + cursorHighlight?: CursorHighlightConfig; + cursorClickTimestamps?: number[]; platform: string; } @@ -387,6 +395,46 @@ export class FrameRenderer { // Composite with shadows to final output canvas this.compositeWithShadows(webcamFrame); + // Cursor highlight overlay (rendered above video, below annotations) + if ( + this.config.cursorHighlight?.enabled && + this.config.cursorTelemetry && + this.config.cursorTelemetry.length > 0 && + this.compositeCtx + ) { + const emphasisAlpha = clickEmphasisAlpha( + timeMs, + this.config.cursorClickTimestamps, + this.config.cursorHighlight, + ); + const cursorPoint = + emphasisAlpha > 0 ? interpolateCursorAt(this.config.cursorTelemetry, timeMs) : null; + if (cursorPoint) { + const cx = cursorPoint.cx + this.config.cursorHighlight.offsetXNorm; + const cy = cursorPoint.cy + this.config.cursorHighlight.offsetYNorm; + const stageX = + layoutCache.baseOffset.x + cx * this.config.videoWidth * layoutCache.baseScale; + const stageY = + layoutCache.baseOffset.y + cy * this.config.videoHeight * layoutCache.baseScale; + const appliedScale = this.animationState.appliedScale; + const canvasX = stageX * appliedScale + this.animationState.x; + const canvasY = stageY * appliedScale + this.animationState.y; + const previewW = this.config.previewWidth ?? this.config.width; + const previewH = this.config.previewHeight ?? this.config.height; + const cursorScale = (this.config.width / previewW + this.config.height / previewH) / 2; + drawCursorHighlightCanvas( + this.compositeCtx, + canvasX, + canvasY, + { + ...this.config.cursorHighlight, + opacity: this.config.cursorHighlight.opacity * emphasisAlpha, + }, + appliedScale * cursorScale, + ); + } + } + // Render annotations on top if present if ( this.config.annotationRegions && diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index f41b58d..0d7a432 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -51,6 +51,8 @@ interface GifExporterConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; + cursorHighlight?: import("@/components/video-editor/videoPlayback/cursorHighlight").CursorHighlightConfig; + cursorClickTimestamps?: number[]; onProgress?: (progress: ExportProgress) => void; } @@ -161,6 +163,8 @@ export class GifExporter { previewWidth: this.config.previewWidth, previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, + cursorClickTimestamps: this.config.cursorClickTimestamps, + cursorHighlight: this.config.cursorHighlight, platform, }); await this.renderer.initialize(); diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index d44bf40..e064ba7 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -42,6 +42,8 @@ interface VideoExporterConfig extends ExportConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; + cursorHighlight?: import("@/components/video-editor/videoPlayback/cursorHighlight").CursorHighlightConfig; + cursorClickTimestamps?: number[]; onProgress?: (progress: ExportProgress) => void; } @@ -156,6 +158,8 @@ export class VideoExporter { previewWidth: this.config.previewWidth, previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, + cursorClickTimestamps: this.config.cursorClickTimestamps, + cursorHighlight: this.config.cursorHighlight, platform, }); this.renderer = renderer;