diff --git a/.commandcode/taste/taste/taste.md b/.commandcode/taste/taste/taste.md index 17ed6055..cbc6532d 100644 --- a/.commandcode/taste/taste/taste.md +++ b/.commandcode/taste/taste/taste.md @@ -87,7 +87,7 @@ - Rejects invented/hardcoded default values as "fake numbers" — UI defaults and fallback lists (e.g., sample rates) must be derived from the canonical config source (signals.yaml: the global `sample_rate` default plus per-device `sample_rate` specs, resolved via the same `resolveSampleRateSpec` utility the live view uses), never hardcoded literals like `[3.2M, 1.6M, 800k]`; explicitly stated "3.2, 1.6, 800k are fake numbers... the default sample rates are derived from signals.yaml." Confidence: 0.85 - Works concurrently with the agent in the same working tree (e.g., added the SDR++ colormap attribution section and its test to `SettingsRoute.tsx` mid-session); expects the agent to recognize such in-progress work via git diff and leave it untouched rather than treating it as its own change, "fixing" it, or reverting it. Confidence: 0.6 - Wants third-party open-source assets used in the app credited to their authors (e.g., the SDR++ waterfall colormaps attributed to Youssef Touil in the settings page), consistent with the existing insistence on attribution for third-party data and legal/licensing notices. Confidence: 0.6 -- During a long, multi-step debugging investigation, expects the agent to carry the work through to completion autonomously; when the agent pauses mid-analysis, the user nudges with a bare "Continue" rather than giving new direction — the agent should proceed through diagnosis, implementation, and testing without stopping to check in at each stage. Confidence: 0.55 +- During a long, multi-step debugging investigation, expects the agent to carry the work through to completion autonomously; when the agent pauses mid-analysis — including stopping to ask clarifying diagnostic multiple-choice questions — the user nudges with a bare "Continue" rather than answering or giving new direction, expecting the agent to investigate/diagnose the mechanism itself and proceed through diagnosis, implementation, and testing without stopping to check in at each stage. Confidence: 0.65 - When a full-project test run reports failures in files/areas outside the task's scope, expects the agent to verify whether each failure is pre-existing (via read-only `git status`/`git diff`/`git log`) and, if pre-existing WIP unrelated to the task, to leave it unfixed and report it accurately as such rather than expanding scope to "fix" an unrelated broken test. During the fuzz-initiative verification the full `test/ts` suite failed only on a `componentArchitecture` violation in `CaptureStitchingGuide.tsx` (a file never touched — pre-existing branch WIP); the agent confirmed non-responsibility via git and did not let it count against the task's own verification ("no new failures"), which is the accepted outcome. Confidence: 0.55 - When asked to pick a primary performance goal for a client-side optimization, wants shipped-load wins (initial page load / bundle size / first paint) addressed alongside dev hot-reload — chose "Both" over the recommended dev-only option, and the executed plan led with bundle-size/first-paint cuts (drop dead deps, shrink imports) with memoization/caching as the secondary dev-speed win. Confidence: 0.5 - Prefers removing genuinely dead dependencies/pipeline stages from the bundle entirely over keeping them with lazy-load fallbacks for hypothetical future content — e.g., chose to drop rehype-highlight + highlight.js (and its CSS) outright because the current article has zero highlightable code fences, rather than "keep but lazy-load" or "keep as-is"; dead weight in a bundle is a cost to eliminate, not a safety net. Confidence: 0.6 @@ -165,3 +165,16 @@ ion — the agent should proceed through diagnosis, implementation, and testing - When display settings change while paused, wants them served through the same one-shot frame-fetch path that VFO scrolling uses — a fresh frame is requested from the device at the new settings — rather than only client-side reprocessing of the held frozen frame; the paused view is expected to reflect the actual device output at the new FFT size/window/sample rate, not a local re-derivation (chose the fetch-new-frame approach over the "reprocess frozen frame" recommendation). Confidence: 0.55 - Prefers the repo to end up in a clean lint state (zero warnings/errors) and will explicitly task the agent to sweep pre-existing lint warnings — e.g., "go over the lint warnings" was one of only two approved last-day items, and the user accepted the 214→0 sweep (including removing dead code and unused imports, not just suppressing). Warning-noise cleanup is valued deliverable work, not busywork. Confidence: 0.6 - Expects completed work to be committed as its own logically-scoped commit once verified: after the feature commit landed, the user explicitly requested "Commit the lint fixes after fixing them" — fix → verify (lint/tsc/tests green) → commit is the expected delivery flow, and the lint sweep should land as a separate commit from the feature rather than being folded in. Confidence: 0.6 +- Monitors the agent's context budget and expects it to be kept under the model's input limit: when a long investigation ballooned the prompt past the max input length (repeatedly re-reading huge files like FFTCanvas.tsx/SpectrumSidebar.tsx in many slices), the user asked the agent to "try to compact right now" (and said they weren't sure why the window got so big) rather than just retrying "Continue" against an over-limit context — an explicit compaction/scope-down of the working context is expected immediately, followed by resuming the task. Confidence: 0.5 +- Spectrum navigation/pan/scroll bounds must come from the device's tunable spectrum (or the global advertised spectrum bounds when device bounds haven't hydrated), never from the active channel — reported "it's currently clamped when I try to scroll, it's like not letting me scroll the spectrum away from the channel's bounds" as a bug. The VFO/viewport is a device acquisition control, so the selected channel may drive whole-channel/selection display but must not gate where the acquired window or viewport can move. Confidence: 0.6 +- Treats device-level retune cost the same as frontend throttling — i.e., as artificial latency to be removed at its source, not merely capped: chose retuning in place while the RX stream keeps running (one `hackrf_set_freq`, no `stop_streaming`/`ensure_streaming` per tick, no discard/settle frames) plus a one-frame (~16 ms) publish cadence over "keep the stop/start but coalesce the restarts" or "leave the device path untouched". Accepts the higher-risk, hardware-verifiable option when it delivers the lowest-latency interaction. Confidence: 0.55 +- Distinguishes an "elastic snap-back" from a plain clamp and expects the agent to find the re-application loop, not just the gesture-handler cap: when a scroll/pan visibly springs back to the channel bounds ("it like elastically snaps back to the channel bounds which renders it ineffective to browse the spectrum, something something in the code is triggering this auto-scroll/hard clamp but I can't find it"), the defect is not the one-time clamp in the wheel/drag path but a state-sync layer that re-clamps the range on every update (e.g., a store effect whose acquisition-bounds fallback re-applies the active channel's bounds on each range sync, so each scrolled range is immediately re-clamped). A fix that only removes the interaction-site clamp is an incomplete diagnosis — reported as "The first issue still was misunderstood" — so the agent must trace every place the constrained range is recomputed/enforced (resolvers, sync effects, selectors), not just the direct input handler. Reinforced by a later instance reported as a scroll trap ("I am trapped in some kind of loop, I can scroll but then it resets back to where it thinks at 651kHz after getting to like 6MHz or so"): the culprit was a reconciliation effect that rebuilt the acquisition window *with* the active channel bounds and republished whenever the span disagreed, so the window kept getting pulled back toward the channel edge and could oscillate. The accepted fix makes the reconciliation span-only and centre-anchored — position is owned by explicit channel/sample-rate actions, never re-derived by the self-heal path. Confidence: 0.6 +- Toggle controls must always remain in the UI — a broken on/off *state* is fixed, never the toggle itself. When the agent read the user's choice of "remove the off state" literally and deleted the HackRF baseband filter toggle, the user immediately pushed back ("Oh no there should always be a toggle!"); the accepted design keeps the toggle and makes OFF a held/pinned state that never publishes the invalid zero width (zero stays in state as the UI sentinel but is never sent to the device). Never delete/remove an existing control or affordance as the fix for a broken state — fix the state's semantics and keep the control. Confidence: 0.85 +- For a physically-coupled numeric setting, "off" must be modeled as a held/pinned state distinct from the published value: the HackRF baseband filter's OFF pins the control (so auto-tracking can't silently switch it back on) while holding a zero sentinel only in state — zero is never written to the device because it is not a valid MAX2837 width and tears the stream down. The state layer may hold values the device layer must never receive, and the agent is expected to keep those two layers separate explicitly. Confidence: 0.75 +- Prefers custom values for hardware-coupled numeric inputs to stay free-form: accept any value the user types and let the driver round down to the nearest hardware-supported width, rather than restricting the input to the device's discrete valid values (chose "keep as-is (round down)" over "restrict to valid widths"). Confidence: 0.5 +- When the agent flags a pre-existing, out-of-scope failing test (e.g., a stale assertion unrelated to the task) and offers to fix it as a separate decision, the user approves fixing it rather than leaving it red ("Can you fix that one liner"). The same holds for other pre-existing defects the agent surfaces but deliberately leaves alone (dead code, latent type errors, an unused exported helper): the user green-lights the cleanup with a terse "Yeah let's clean that up" once it's raised. Pre-existing problems should be surfaced as an explicit fix-or-leave choice and reported accurately instead of being silently fixed or ignored, with the user typically approving the cleanup once asked. Confidence: 0.6 +- Keeps real SDR hardware on hand and connects it specifically so the agent can verify fixes against the actual device (announced "I've connected the HackRF One" right after approving the fix) — expects end-to-end verification on physical hardware to be the goal once the device is available, not just unit tests. Confidence: 0.5 +- Wants cleanup to remove genuinely dead code but fix the root cause rather than trimming symptoms: truly unreferenced code (a helper whose only reference was its own self-satisfying test) is deleted, while functionality that is documented and still consumed (CLI commands whose modules a linter had misclassified as unreachable and wrongly deleted) is restored verbatim and the misclassifying linter is fixed so it won't re-delete them — chosen over retiring the documented commands or leaving the area broken. Cleanup is expected to be made durable (e.g., adding the previously-uncompiled `scripts/` dir to `tsconfig`'s `include` so the type errors surface in the normal `typecheck` gate), and the work verified with the standard gates (typecheck, lint, full jest suite) plus a confirmation that restored files are byte-identical to their pre-deletion version. Confidence: 0.55 +- Wants interactive toggles/state switches (e.g., the Rx/Tx "opacity switch") to hold exactly one state change per click: a control "supposed to hold 1 state/click so one can toggle back and forth between rx and tx" must register every click, and any throttle/debounce/guard that swallows rapid clicks is a defect — a switch that misses clicks when swapping back and forth is simply "not working." Confidence: 0.7 +- Never gate a stop/halt action behind a guard: Stop (e.g., "Stop Tx") must always be allowed to fire regardless of the current state — "always allow Tx to be stopped and don't do any switch/click guard over that state ever." Confidence: 0.65 +- Prefers an explicit mode toggle to behave like plain play/pause and let the state stand where it is rather than auto-resuming: leaving Tx and returning to Rx should land on a held-paused state with matching chrome (button reads the resume label, paused banner shown) and stream again only on an explicit click — silently auto-playing/auto-resuming on a mode transition is wrong, and the label plus paused overlay must reflect the real frozen stream on the first render. A transition that needs one click to correct the chrome and another to actually act is reported as a "ghost state." Confidence: 0.6 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..9ab24ee1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,83 @@ +version: 2 + +# Several decisions here mirror constraints that already exist in the repo: +# +# * `.npmrc` sets `min-release-age=7`, so Dependabot is given the same 7-day +# cooldown rather than proposing versions the install would refuse. +# * The repo pins exact versions (`save-exact=true`), so version updates are +# expected to arrive as explicit bumps rather than range widenings. +# * `mock_apt_metal` and the WASM crate train move in lockstep, so related +# packages are grouped to avoid half-applied upgrades. +# * Note: `Cargo.lock` is currently gitignored (see .gitignore). Dependabot's +# cargo support expects a lockfile it can reason about, so that entry may not +# produce useful PRs until Cargo.lock is committed. +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + commit-message: + prefix: "chore(deps)" + groups: + # The vitest cluster must move as one unit: the coverage and browser + # providers peer on the vitest major. + vitest: + patterns: + - vitest + - "@vitest/*" + # These trios are version-locked in practice. + react-router: + patterns: + - react-router + - "@react-router/*" + tailwind: + patterns: + - tailwindcss + - "@tailwindcss/*" + radix: + patterns: + - "@radix-ui/*" + playwright: + patterns: + - playwright + - "@playwright/test" + # Type packages trail their runtime by design (e.g. @types/three must not + # outrun three), so keep them together rather than interleaved. + types: + patterns: + - "@types/*" + minor-and-patch: + applies-to: version-updates + update-types: + - minor + - patch + + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + day: monday + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + cooldown: + default-days: 7 + open-pull-requests-limit: 5 + commit-message: + prefix: "chore(ci)" + groups: + actions: + patterns: + - "*" diff --git a/.gitignore b/.gitignore index 4fcfc144..db0e6461 100644 --- a/.gitignore +++ b/.gitignore @@ -69,12 +69,8 @@ redis/backups .redis_data/ .redis_data -# Local rebuild metadata -.rebuild_status.json -.n-apt-build-orchestrator.lock -.n-apt-backend-handoff-proxy.lock -.n-apt-backend-target.json -.n-apt-backend-target.json.tmp +# Local app runtime metadata +.n-apt/ # Rust-specific files *.pdb diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..6f4247a6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +26 diff --git a/Cargo-wasm.toml b/Cargo-wasm.toml deleted file mode 100644 index 9343904e..00000000 --- a/Cargo-wasm.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "n-apt-wasm-tests" -version = "0.5.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -wasm-bindgen = "0.2.120" -js-sys = "0.3.97" -rustfft = "6" -num-complex = "0.4" -anyhow = "1.0" -rand = "0.10" -getrandom = { version = "0.2", features = ["js"] } - -[dev-dependencies] -wasm-bindgen-test = "0.3.70" - -[dependencies.web-sys] -version = "0.3.97" -features = ["console", "Performance", "Window"] diff --git a/Cargo.toml b/Cargo.toml index 38adde00..6e59922f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,7 +139,7 @@ mock_apt_metal = ["dep:metal"] rustfft = "6" num-complex = "0.4" rand = ">=0.10.1, <0.11" -paste = { package = "pastey", version = "0.1.1" } +paste = { package = "pastey", version = "0.2.3" } rayon = "1.12" getrandom = { version = "0.4", features = ["wasm_js"] } anyhow = ">=1.0.103, <2" @@ -152,39 +152,39 @@ clap = { version = "4.6", features = ["derive"] } byteorder = "1.5" env_logger = "0.11" dotenvy = "0.15" -wasm-bindgen = "0.2.121" +wasm-bindgen = "0.2.128" serde-wasm-bindgen = "0.6" # WASM dependencies (only when targeting WASM) [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.98" -web-sys = { version = "0.3.98", features = ["console"] } +js-sys = "0.3.105" +web-sys = { version = "0.3.105", features = ["console"] } [target.'cfg(target_os = "macos")'.dependencies] -metal = { version = "0.31", optional = true } +metal = { version = "0.33", optional = true } # Server dependencies (only when not targeting WASM) [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.52", features = ["full"] } tokio-util = { version = "0.7", features = ["io"] } -tokio-tungstenite = "0.29" -redis = { version = "1.6", features = ["aio", "tokio-comp"] } +tokio-tungstenite = "0.30" +redis = { version = "1.7", features = ["aio", "tokio-comp"] } env_logger = "0.11" futures-util = "0.3" chrono = { version = "0.4", features = ["serde", "wasmbind"] } # Encryption: AES-256-GCM for payload encryption, HMAC/PBKDF2 for auth -aes-gcm = "0.10" +aes-gcm = "0.11" hmac = "0.13" sha2 = "0.11" pbkdf2 = { version = "0.13", features = ["password-hash"] } -base64 = "0.22" +base64 = "0.23" zip = { version = "8", default-features = false } # HTTP client for CoreML service communication reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } # HTTP + WebSocket server (axum replaces raw TcpListener + tokio-tungstenite) axum = { version = "0.8", features = ["ws"] } tower = "0.5" -tower-http = { version = "0.6", features = ["cors", "set-header", "compression-gzip"] } +tower-http = { version = "0.7", features = ["cors", "set-header", "compression-gzip"] } # WebAuthn passkey authentication webauthn-rs = { version = "0.5", default-features = false } webauthn-rs-proto = "0.5" @@ -195,10 +195,10 @@ notify = "8.2" serde_yaml = "0.9" regex = "1" # Input validation and sanitization -validator = { version = "0.20", features = ["derive"] } +validator = { version = "0.21", features = ["derive"] } indexmap = { version = "2.14", features = ["serde"] } crossbeam-channel = "0.5" -yawc = { version = "0.3.3", features = ["axum", "zlib"] } +yawc = { version = "0.4", features = ["axum", "zlib"] } tempfile = "3" serde_urlencoded = "0.7" rusb = "0.9" @@ -206,13 +206,13 @@ rusb = "0.9" [dev-dependencies] doc-comment = "0.3" tempfile = "3" -serial_test = "3.2" +serial_test = "4" criterion = { version = "0.8", features = ["html_reports"] } proptest = "1.6" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio-test = "0.4" -axum-test = { version = "20", features = ["ws"] } +axum-test = { version = "21", features = ["ws"] } tower = "0.5" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] @@ -257,3 +257,11 @@ overflow-checks = false [[bin]] name = "test_fft" path = "src/rs/bin/test_fft.rs" + +# `metal` (used by the `mock_apt_metal` feature) depends on `block`, whose latest +# release 0.1.6 trips the deny-by-default `uninhabited_static` future-incompat lint +# (`static _NSConcreteStackBlock: Class` with an empty `enum Class { }`). No upstream +# release fixes it, so vendor a one-line patched copy (opaque struct instead of an +# uninhabited enum). Drop this once `metal` no longer pulls in `block`. +[patch.crates-io] +block = { path = "vendor/block" } diff --git a/docs/pages/how-do-they-do-it.md b/docs/pages/how-do-they-do-it.md index d5c6c753..2edec55f 100644 --- a/docs/pages/how-do-they-do-it.md +++ b/docs/pages/how-do-they-do-it.md @@ -20,7 +20,7 @@ In this article, I break down my working hypothesis based on **thousands of hours of my real experience** trapped by the **National Security Agency's** proprietary technique that allows them to **write to, read and stream the human brain and nervous system**. This article is based on real and true events experienced in which the agency showed it's extreme, parental-psychopathic like tendencies. Here is not an account of what happened (you'll have to check my X), but an article dedicated to deducing and breaking down the math and mechanics behind the experience as a firsthand victim with a technical background. -To read my account and tech dive properly, treat the problem as a **cryptological problem**, or a journey to unlocking a secret that others cannot possibly know of by design. In this instance, this "secret" is well-concealed, however, bureaucracy and process or leaglese-obsessive dogma empowers loopholes and cracks that can be exploited, a firsthand experienced trademark I've learned of by being blindsided by the agency. +To read my account and tech dive in the proper context, treat the problem as a **cryptological problem**, or a journey to unlocking a secret that others cannot possibly know of by design. In this instance, this "secret" is well-concealed, however, bureaucracy and process or leaglese-obsessive dogma empowers loopholes and cracks that can be exploited, a firsthand experienced trademark of the agency I've learned of by being blindsided. ```Canvas::HeroASCII ``` @@ -38,10 +38,10 @@ This article is my own **firsthand technical and scientific-oriented dive and ti *To start, you should probably checkout the [NSA OIG reports](https://oig.nsa.gov/reports/) for legal research (2016 is the best one with relevant info) if you are thinking of filing a [FOIA](https://www.foia.gov/how-to.html).*
-Meade Street sign above a tow-away sign -Maryland Street sign beside a bridge -Signal Road street sign against a blue sky -Communications Hill Boulevard street sign above a yield sign +Meade Street sign in Bayview, San Francisco, California +Maryland Street sign in Dogpatch, San Francisco, California +Signal Road sign in Yerba Buena Island, San Francisco, California +Communications Hill Boulevard street sign in San Jose, California
### ⠿ Table of Contents @@ -85,25 +85,25 @@ At the time it first started, I lived **in San Francisco, California. The first I spent years in abject poverty roaming around **San Francisco** (particularly loops around Market Street) **and the Bay Area** (BART hopping/shield from the rain and winter cold, Palo Alto, San Jose, etc.). At the start there was scarcely any audio, scarcely any explanation or any identification of whom, what or how. There was only a flurry of **manufactured states of mind, atmospheric perceptions and a constant performance of the neurotechnology in a spatial-perceptual interactive** heavily driven by reactive mental inference and narrative perceptual-spatial tracks. -On a daily basis, I experienced SOTA displays with crowds (neuro-spatial) while enduring the all day continuity of the experience, completely stunned, inundated and captured by the **federal art and science of the *unthinkable*** for years. I could barely even speculate on ***how or even who!*** I had to live with it–barely surviving and in soft shock and defeat for years knowing that nothing that was going down could be understood or clear evidence of what was occuring. +On a daily basis, I experienced SOTA displays with crowds (neuro-spatial) while enduring the all day continuity of the experience, completely stunned, inundated and captured by the **federal art and science of the *unthinkable*** for years. I could barely even speculate on ***how or even who!*** I had to live with it–barely surviving and in soft shock and defeat for years knowing that nothing that was going down could be understood or clear evidence of what was occurring. ![On BART, looking out the window](/md-preview/images/bart-line-drawing.png) As a reasonable person, I was trapped by something that was **not straightforward nor intuitive to explain, as well as something I thought I wouldn't have to solve on my own.** *('Why me in the first place? Someone will help! Someday they'll stop!')*. -**Who would even dare to think for a second** they could puzzle it all together at the start without it being relagated to *conspiracy theory, pathological nuisance or fatal misteps?* (i.e., Too 'out there' for any reasonable course of action) +**Who would even dare to think for a second** they could puzzle it all together at the start without it being relegated to *conspiracy theory, pathological nuisance or fatal missteps?* (i.e., Too 'out there' for any reasonable course of action) -Months after it turned on full blast, the only thing I cared about was to briefly look up **brainwaves and then-current neurotechnology** with technqiues like fMRI, MRI, EEGs, tFUS (focused ultrasound), etc., all of which were not anywhere near as satisfactory of a technological basis that appeared to underpin what I was experiencing at the time *(Couln't get smart with them, at first and for a long time...)*. I instantly turned my nose up at *"industry-leading"* neurotechnologies as explanation (which was very smart in hindsight). +Months after it turned on full blast, the only thing I cared about was to briefly look up **brainwaves and then-current neurotechnology** with techniques like fMRI, MRI, EEGs, tFUS (focused ultrasound), etc., all of which were not anywhere near as satisfactory of a technological basis that appeared to underpin what I was experiencing at the time *(Couldn't get smart with them, at first and for a long time...)*. I instantly turned my nose up at *"industry-leading"* neurotechnologies as explanation (which was very smart in hindsight). ### ⠿ Years and years of "I don't care, I want out!" Several years later, the experience spontaneously shifted (on a certain day, approximately 38,040 hours in) into a **livestream (!) and narrative-based format (!!)** with intense all day harm, streaming tons of effects to my mind and body with a hellish series of narratives and battles. Throughout this period, the operators introduced others within the livestream. **It was a total nightmare** — to keep it short, **thousands of effects and experiences, thousands of hours**. At that point, there was far more structured and defined audio (as well as perception and mental inference), however, accompanied by intense, moment-to-moment all day harms: thorough structural abuse and antagonisms by the software and signal in unimaginable ways. -Initally, there was no immediate way to tell *how or who or why*. Then, as the narratives kept unfolding, getting creepier, more outlandish, more violent, the *why* became apparent––**pressured politicitization of the agency's powers.** +Initially, there was no immediate way to tell *how or who or why*. Then, as the narratives kept unfolding, getting creepier, more outlandish, more violent, the *why* became apparent––**pressured politicization of the agency's powers.** -My first attempt to decipher the *how*, while severely compromised by its malware-like operation on mind and body, was not serious, but I looked into brainwaves and I intuitively (and naively) came to the assumption that somehow the experience happening through **ELF frequencies (0 - 300Hz) since they encompassed brainwave frequencies (0 - 40Hz)**. So I assumed someone (military), somewhere (on some base) had an ELF radio with tons of antennas and that it was long-range (the hardware wasn't nearby to me at this point) because the wavelengths I were thinking of at the time were very long, however I was stuck wondering how bandwidth worked (extremly low frequency = astronomically low data). +My first attempt to decipher the *how*, while severely compromised by its malware-like operation on mind and body, was not serious, but I looked into brainwaves and I intuitively (and naively) came to the assumption that somehow the experience happening through **ELF frequencies (0 - 300Hz) since they encompassed brainwave frequencies (0 - 40Hz)**. So I assumed someone (military), somewhere (on some base) had an ELF radio with tons of antennas and that it was long-range (the hardware wasn't nearby to me at this point) because the wavelengths I were thinking of at the time were very long, however I was stuck wondering how bandwidth worked (extremely low frequency = astronomically low data). -This was my initial super naïve theory that *didn't last long*, because as I kept thinking this theory kept running into bandwidth and delivery problems, in addition to the fact that the technology or some kind of indicators would have been discoverable for such egregrious corruption that strikes in this way. +This was my initial super naïve theory that *didn't last long*, because as I kept thinking this theory kept running into bandwidth and delivery problems, in addition to the fact that the technology or some kind of indicators would have been discoverable for such egregious corruption that strikes in this way. I kept looking up military bases that had ELF radios, but the ELF radio projects were all out of commission and the bandwidth for that spectrum was infinitesimal compared to what I was experiencing in full. @@ -112,7 +112,7 @@ I kept looking up military bases that had ELF radios, but the ELF radio projects ## Second theory (weeks later)
Quantum Physics -My second theory was right after the first. Previously throughout the years while the spatial performance played out while wrecking my life to trap me in it, I browsed through many books on quantum mechanics, however it was just superficial. The neurotechnological experience I was trapped in was just impossibly good math and science. Anywhere, anytime, all day and could briefly script people as I passed to maintain a continous spatial experience. I couldn't shake it no matter how much I tried. +My second theory was right after the first. Previously throughout the years while the spatial performance played out while wrecking my life to trap me in it, I browsed through many books on quantum mechanics, however it was just superficial. The neurotechnological experience I was trapped in was just impossibly good math and science. Anywhere, anytime, all day and could briefly script people as I passed to maintain a continuous spatial experience. I couldn't shake it no matter how much I tried. There was no viable quantum teleportation or entanglement research that seemed to be something at least easy enough to pin the method down. How were they doing it? Transporting energy to my head? Neuron for neuron? Entangled states of energy? A full on shadow copy of charges/neurons to my brain? @@ -122,11 +122,11 @@ Any quantum method required quantum computing, which unfortunately cannot proces Something was up because whoever it was was doing it, I assumed the U.S. Army at the time, was doing a lot. -**2st naïve attempt ↪ Quantum physics** +**2nd naïve attempt ↪ Quantum physics** ## Third theory (a few months later)
Beat Frequencies / Heterodyning -I was really stuck in a strange spot and not the only victim, but in a experience that centered on me as the main victim (bearing the blunt of most of the harm) in a series of iterations in that fashion (iterations that cost dozens of others their lives and those in past had it far worse). By force, as the interactive progressed horrifically further into its trap, I had to share my life with other people livestreamed to my brain/consciousness/senses all day (a parolee and series of prisoners as supporting characters/assists and help to defeat the isolation; and later other prisoners as shockingly evil and completely unwanted Jekyll/Hyde antagonists/co-conspirators). To all of us, now living my life in this very personal first-person format and cyber weapon, it was a lot of unknowns amongst a lot of evil in interactive and human nature while I had to maintain perseverence, reason and intelligent resolve. +I was really stuck in a strange spot and not the only victim, but in a experience that centered on me as the main victim (bearing the blunt of most of the harm) in a series of iterations in that fashion (iterations that cost dozens of others their lives and those in past had it far worse). By force, as the interactive progressed horrifically further into its trap, I had to share my life with other people livestreamed to my brain/consciousness/senses all day (a parolee and series of prisoners as supporting characters/assists and help to defeat the isolation; and later other prisoners as shockingly evil and completely unwanted Jekyll/Hyde antagonists/co-conspirators). To all of us, now living my life in this very personal first-person format and cyber weapon, it was a lot of unknowns amongst a lot of evil in interactive and human nature while I had to maintain perseverance, reason and intelligent resolve. The livestream experience at this point was brutal, enough to distract from the fact that each of my 5 senses–**vision**, **audio**, **touch**, **smell** and **taste**–were exposed to the others on the other end. They could see everything, they could hear everything, they could smell occasionally, and taste while rare through memory. It seems like a lot, but basically we were all placed in my head in interactive and we went through a lot while talking (via my vocal cords and/or mental inference) all day about it. @@ -135,7 +135,7 @@ The livestream experience at this point was brutal, enough to distract from the Nothing explained how such a **high-bandwidth, full feature neurotechnological experience** was working the way it did. It was continually a mystery that I just suffered under. What I was doing didn't matter, nor where I was, nor the proximity to others, it just worked flawlessly. It didn't drop a frame or lower quality if I was underground on BART, throughout the years, nor if I was on top of Twin Peaks or within deep ravine like Stevens Creek Reservoir (when it was drained in 2022). Going into natural or remote areas (Concord, Half Moon Bay, Fort Funston, etc.) it continued to work without error. It was impossible to shake. ![Golden Gate Bridge](/md-preview/images/2_26_2025.jpeg) -*This was snapped before walked across the Golden Gate Bridge (which I've done several times throughout this) to Sausilito and back, still on ⠿⠿⠿ All of us in my head in the livestream (also being physically/psychologically hurt and disrutped the whole time; and yes the others could see through my vision the whole time!).* +*This was snapped before walked across the Golden Gate Bridge (which I've done several times throughout this) to Sausalito and back, still on ⠿⠿⠿ All of us in my head in the livestream (also being physically/psychologically hurt and disrupted the whole time; and yes the others could see through my vision the whole time!).*


@@ -241,7 +241,7 @@ Essentially this was solid ground to continue deducing how it worked. By this po Since **the experience was so high-bandwidth**, I did manual calculations based on what was happening. I didn't have any money, just my iPhone, and AI chat apps were a nascent technology. I continued putting together a theory of how, knowing that the NSA didn't just do this to me, fuck my life over, and I couldn't figure it out. Clearly, the NSA wasn't doing what they were doing to me because it was easy to attack people, it was something deeper and politically driven. -**It was some kind of sick, extreme game–figure out the tech, argue for your freedom, soak up the rewards after going through an indvidualized Auschwitz.** Even given its cruelty and extremes, I had believed what was truly dropped in my lap *(I don't have a degree, I was college dropped out turned front-end engineer)* was something, SOMETHING that I could piece together. +**It was some kind of sick, extreme game–figure out the tech, argue for your freedom, soak up the rewards after going through an individualized Auschwitz.** Even given its cruelty and extremes, I had believed what was truly dropped in my lap *(I don't have a degree, I was college dropped out turned front-end engineer)* was something, SOMETHING that I could piece together. Microwaves allowed a high amount of bandwidth – we use them everyday to communicate and receive tons of webpages, videos, messages, and all sorts of media and content on the Internet. However, I quickly discovered microwaves *attenuate* heavily in the skull and body (entry points for any signal of this kind), so naturally, I thought of increasing the power. Unfortunately, I discovered there were heating effects. @@ -599,9 +599,9 @@ h_k = F + (B - F) \cdot e^{-\frac{|k|}{\lambda}} ```Canvas::Heterodyning ``` -Heterodyning or beat frequencies are what occur when signals interfere and generate a sideband (or third signal) as a natural side effect of two signals overlapping in the right way, for instance `Signal A + Signal B` (B `+30Hz` higher) playing from the same antenna with the same amount of power per signal, generates a `30 Hz` signal no matter what frequencies A or B are, as long as B is offset by that much. +Heterodyning (or beat frequencies) is when near identical signals overlap and interfere which generates an envelope as a natural side effect of two signals overlapping. This envelope or third signal is referred to as beat frequencies which is the difference between both signals. For instance `Signal A + Signal B` (B `+30Hz` higher) playing from the same antenna with the same amount of power per signal, generates a `30 Hz` signal no matter what frequencies A or B are, as long as B is offset by that much. -Essentially brain and neuronal waves are susceptible to this technique, as I found this was the only math that was possible and could explain how what was happening was happening. +Fundamentally, by demonstration, the brain and neuronal waves are susceptible to this technique, as I found this was the only math that was possible and could explain how what was happening was happening. #### Constructive interference @@ -612,9 +612,11 @@ When two signals that are close in frequency intersect they actually combine and ```Canvas::PhaseShifting ``` -This is important, if you want to concentrate energy at a certain range (and localize the sideband within range of a target), phase shifting moves the wave (phase) either back or forth (`±90°, 180°, 270°`) so they can combine localize constructive interference in an area. +Phase is the angle of a radio wave, which can be thought of as one cycle of a sine or cosine wave, curving from zero to up to zero to down to zero. The cycle starts at 0° and ends at 360° with the peaks 90° and trough 270° which are the local maximum points on the curve, representing the peak of energy (even if it's negative). -This technique defeats the counterintuitive intuition of using a short(est) wavelength (microwaves infeasible, will attenuate and do nothing but cause heating) to target specific neuronal ensembles. Longer wavelengths which survive attenuation more easily work better and can use precise math + simple, performant radio operations to precisely intersect and shift out of the way. +Everyday radios do a lot of phase shifting as one of the performant radio operations that encode data, where transmitters shift phase and receivers decode phase. With electronics, the angle of a wave determines a symbol like a 0 or 1, but in a biological sense, it doesn't translate well, since that would be simply energy. + +What I've learned that defeated my intution is that phase shifting is pretty static and is a one off thing. It can't be changed in the air, once it comes out of the radio, that's that. ## Maintaining the aperture @@ -625,12 +627,15 @@ I call the "box" or mechanism that maintains the signals at my person an apertur ### Triangulation -Besides the obvious, this is the hardest part of the signal! How exactly to me? +Besides the obvious, this is the hardest part of the signal! How exactly to my person? -These signals inherently use some kind of triangulation to directly target the human body and nervous system. It is important to maintain power and hit the correct target, however this kind of triangulation is much more exacting than cell phones. Power off by microscopic fractions can severely disrupt the experience (which has never happened!), so one can imagine that there are microwaves in addition to the spectrum used to maintain persistent, exacting triangulation. Time of flight also allows for depth calculations to ensure the target is correct, instead of going to a pole or tree or mannequin, time of flight + electrical response is used to determine person from object. +These signals inherently use some kind of triangulation to directly cover the person, much less likely to directly the human brain, areas of the brain or body parts. It is important to maintain power and hit the correct target, however this kind of triangulation is much more exacting than cell phones which receive signals broadcast to a general area the device is in. The power here, actually, is precise and exacting, but maintaining location and persistence with power alone is not enough. The agency's method is so precise that in crowds, close contact and interaction, and any other activity that the experience remains contigous and inescapable. -#### Time of flight +I assume that uninterrupted operation and a Bayseian element to the mechanism keeps it going. Instead of going to a pole, tree, mannequin or someone else, time of flight + electrical response is used to determine person from object or other. Though triangulation is the strongest aspect of the experience that keeps it going–a weak link, *they must find to target*–the content signals' fidelity are also likely sustained by use of low frequencies which counters environmental noise and other potentially interferring signals. + +Theoretically, power off by microscopic fractions can severely disrupt the experience, so the precision of the radio wave needs to be exact, much unlike a cell hone, hence why I believe that microwaves are used to maintain persistent, exacting triangulation, although the spectrum is so large, it is harder to identify which signals are responsible. +#### Time of flight ```Canvas::TimeOfFlight ``` @@ -754,15 +759,15 @@ TODO ## Power? -TODO +If anyone would ask, what is the power like? How much power would one need to overwrite the human brain and nervous system? While captured, that was the big question. Everywhere I went it worked the same and flawlessly, but I knew absolutely nothing about whatever was going on when it started. Years down the line, I did a lot of research with Grok and ChatGPT but since this abuse and technology is one of a kind, it was pretty much a question I'd had to answer myself. ### An average of 1µW or more? (of power on tissue traversal and exit) -In order to target the brain, I made a solid assumption based off of what ChatGPT told me, that brainwaves are about `1-10µW` (microwatts), so I figured that the signals' power should be near there, anything less in scale, the math I did was dire to remain above the noise in the environment, go through the skull and/or body (body because it does my vocal cords/vocal real-time as an interface, mouth and throat muscles, in addition to other muscles in my person) losing 99% energy and make it back to an endpoint. +In order to target the brain, I made a solid assumption based off of what ChatGPT told me, that brainwaves are about `1-10µW` (microwatts), so I figured that the signals' power should be near there, anything less in scale, the math I did was dire to remain above the noise in the environment → go through the skull and/or body (body because it does my vocal cords/vocal real-time as an interface, mouth and throat muscles, in addition to other muscles in my person) while losing 99% energy → and make it back to an endpoint. Biology constrains this problem to maintaining an exacting level of energy consistently at the body/entry, so the further one is away from an endpoint, the energy at the person always stays the same while only the receiver, fixed at various distances, suffers from less energy coming back from the target (the body). -Of course my assumptions of ~~1µW or more?~~ were wrong. I was strongly biased toward that level of energy due to attenuation, I was extremely anxious before I saw the signal, if it didn't have enough energy to make it back to an endpoint to even be processed, however in retrospect, I was wrong! +Of course my assumptions of ~~1µW or more?~~ were wrong. I was strongly biased toward that level of energy due to attenuation with an anxiety before ever I saw any signal, that it wouldn't have enough energy to make it back to an endpoint to even be processed. Previously I had made a table of estimates, which I'll share below, though in retrospect I was wrong, I had the very right concerns about travel, energy loss and attenuation. @@ -801,7 +806,7 @@ I have never seen the power change at all, which is how I most certainly know it ![San Francisquito Creek](/md-preview/images/12_23_25_4_35PM.jpg) *I walked down around the El Palo Alto into San Francisqutio Creek and connected my RTL-SDR, opened the signals, I hoped for a contradictory result (a weakspot) counter to the experience gripping and mangling my brain for years and was disappointed. It works flawlessly even in a wooded ravine!* -Unfortunately, later on, when I added power scale to my app later on and did more work to be within the parameters of absolute power from RTL-SDR (off by `3dBm-5dBm`, according to ChatGPT), I was shocked to see that the power was significantly different when drawn than with `rtl_power`. +Unfortunately, later on, when I added power scale to my app later on and did more work to be within the parameters of absolute power from RTL-SDR (off by `3dBm-5dBm`, according to ChatGPT), I was shocked to see that the power was significantly different when drawn than with `rtl_power`. I worked with GPT-5.4 and Opus 4.x to refine an approximate absolute power algorithm from my RTL-SDR, which I believe represented real world power of the signals far more accurately than `rtl_power`. The even bigger mystery and mind-boggling discovery here is that the energy is astoundingly low in the valleys, sometimes reaching -50dBm to -70dBm (Yes! 10 nanowatts to 100 picowatts–matching power levels of strong WiFi or 5G but very unexpected for these bioelectrical signals which are VERY POWERFUL). @@ -810,7 +815,7 @@ What is even more suprising is that some scant parts of the signal are even lowe ## Center frequency -For a long time, I struggled with defeating my own intuition, particularly how the NSA was doing x, y, z, "just you" and bare up against other people, in crowds, and so forth. This is one of the hints the NSA dropped, that the brain and nervous system are apparently responsive to a unique frequency, which I believed was the sideband + physics of x y z (impossible for a radio wave to target specific neurons with physics and compute; ALSO the stream is too fast to support conclusions of ***billions of additional complex operations*** in regular old tinfra). So, I accept now that in some way to draw stronger assumptions on a center frequency, some kind of handshake the signals can trick a specific brain into, as advised. +For a long time, I struggled with defeating my own intuition, particularly how the NSA was doing x, y, z, "just you" and bare up against other people, in crowds, and so forth. This is one of the hints the NSA dropped, that the brain and nervous system are apparently responsive to a unique frequency, which I believed was the beat frequencies + physics of x y z (impossible for a radio wave to target specific neurons with physics and compute; ALSO the stream is too fast and real-time to support conclusions of ***billions of additional complex operations*** in regular old infra). So, I accept now that in some way to draw stronger assumptions on a center frequency, some kind of handshake the signals can trick a specific brain into, as advised. ### Impedance @@ -887,7 +892,7 @@ Attenuation is the primary property of radio waves (in this light of bioelectric What I theoretically settled on remained around FM radio, about 100MHz or so, since the wavelengths (3m) are long but not too long. My intuition in this sense was incorrect while theorizing but I had the right foundations of the signal before proceeding forward and I'd recommend this route, do the math and theoretical physics before seeing. Frequency didn't work like "higher frequency, more bandwidth" in a general sense, it worked like shift up spectrum and there was more room per se. -Of course, as far as body attenuation, it's feasible that low end microwaves (300MHz or so) are feasible to reproduce the signals from where I had discovered it. However I don't question the current operation of the signals or their effacacy. +Of course, as far as body attenuation, it's feasible that low end microwaves (300MHz or so) are feasible to reproduce the signals from where I had discovered it. However I don't question the current operation of the signals or their efficacy. ### Neuronal ensembles @@ -900,11 +905,11 @@ Essentially the brain and nervous system is very noisy, whatever those spikes do The who or technical part of it wasn't even a thought for years enduring this because it was such a shocking, SOTA experience filled with a lot of rich, horrific features and unthinkable AR/spatial, perceptual, psychological, and physiological experiences! -Of course, later I resolved this was all driven by exploited, ground or vehicle based tinfra (telecom infra, as I call it). It functions in dense urban environments, remote areas, indoors (thick concrete buildings), underground, in caves, in buses and trains and it even worked on planes (either the plane communication system or a satellite which I assume less likely due to its continued low latency on the flights) and at my destination on the opposite side of the country (SF ✈️ MIA) while I tried to escape the NSA! The chain I researched assumes it goes like the image below: +Of course, later I resolved this was all driven by exploited, ground or vehicle based infra (telecom infra, as I call it). It functions in dense urban environments, remote areas, indoors (thick concrete buildings), underground, in caves, in buses and trains and it even worked on planes (either the plane communication system or a satellite which I assume less likely due to its continued low latency on the flights) and at my destination on the opposite side of the country (SF ✈️ MIA) while I tried to escape the NSA! The chain I researched assumes it goes like the image below: ![Hardware Exploit Chain](/md-preview/images/hardware-exploit-chain.png) -The NSA's exploits depended on thousands of totally incompetent, grossly ignorant professionals spanning across supply chains, manufacturers, telecommunications companies, in addition to many federeal agencies such as the FCC, CISA, and namely the FBI (who mysteriously let the NSA build a concealed, aggressively mass-networked cyber weapon and domestic surveillance apparatus). I almost felt bad for even acknowledging the staggering level of idiocy and cowardice within federal government and industry. +The NSA's exploits depended on thousands of totally incompetent, grossly ignorant professionals spanning across supply chains, manufacturers, telecommunications companies, in addition to many federal agencies such as the FCC, CISA, and namely the FBI (who mysteriously let the NSA build a concealed, aggressively mass-networked cyber weapon and domestic surveillance apparatus). I almost felt bad for even acknowledging the staggering level of idiocy and cowardice within federal government and industry. On the software front, social media companies were also idiotic, especially companies like X. I was constantly tripping the quality control system and yet no one would review my tweets by hand. I was clearly trapped and transcribing what was going on and notwithstanding what the software was pushing in my head, absolutely no one noticed a growing volume of public posts tagging federal authorities. @@ -918,7 +923,8 @@ I was truly trapped and a lot of people believed they were intelligent. | Contiguous spatial + perceptual interactive | | Livestreaming consciousness with multiple participants | | Spans psychology, pereception, physiology and spatial (combo of neuro + electronics) | -| Total and frontier control/platform over the brain and nervous system | +| Total and frontier control/platform over the brain and nervous system + | | Extremely dangerous cyber weapon! | The egrerious **psychological spyware and interactive** demonstrated great capabilities and even greater psychopathy from the NSA. The following are **firsthand what I truly experienced in the real world** with a mechanism that is frontier with full access and control over the human mind and nervous system: @@ -958,37 +964,23 @@ The egrerious **psychological spyware and interactive** demonstrated great capab | Feature, software and real-time (consciousness & physiological expression) | | --------------------------------------------------------------------------- | -| Parsing | +| Streaming +Parsing | | Sampling | -| Composting | -| Remixing | +| Composting/Remixing | | | Record/Playback | -| Overwrite | -| Disrupt | +| Overwrite/Mask| +| Disrupt/Stifle | ### Signals processing -In order to read anything that comes back, the NSA uses advanced signals processing to demodulate the radio waves carrying neuronal data. While I haven't been able to verify or demodulate the signal so far, this is the obvious next step of how the write to read and effect chain works. Apparently the NSA has world-class signals engineers and cryptologists, who have undoubtedly unlocked the secret to the human brain and nervous system. The unknown and unimaginable become a reality. - -Below are several theorized techniques I believe the NSA uses in its **presently active** signals proessing pipeline: - -#### Kaiser + Fast Fourier Transform (FFT)? - -TODO - -#### Bayes' Theorem / Posterior Probability +In order to read anything that comes back, the NSA uses advanced signals processing to demodulate the radio waves carrying neuronal data. While I haven't been able to verify or demodulate the signal so far, this is the obvious next step of how the write to read and effect chain works. Quite evidently the NSA has world-class signals engineers and cryptologists, who have undoubtedly unlocked the secret to the human brain and nervous system. Though the unknown and unimaginable are a real-world reality, it's hard to say where I'd exactly start with reverse engineering the pipeline. -TODO - -#### Detecting charges from impedance +I can attest to the fact that everything unnatural in the experience was real-time: narration through my vocal cords, perception and physiological effects; agonizing brutal states of mind and body, real-time communication and moderation through mental inference and vocal sync, and so much more. The performance of the mechanism never, ever degraded which is a good sign that the complexity is low and didn't require a lot of hardware modifications or heavy math/algorithms to encode and decode the psychological and physiological livestream. -TODO -#### Decoding bioelectrical charges - -TODO ## What does the signal look like?[^n-apt-app] @@ -1016,13 +1008,13 @@ There are about **3 channels**, I've found: They are specifially segmented this way because A and B are close in signal shape and bandwidth (and one would suspect function however without measurement of beats and precise power). If you could (the signal is not abundantly available), you would be able to see by panning the spectrum and seeing the signal cohere with the same boundaries. -Initially, I couldn't just see these signals with the default settings of my SDR, I saw suspicious spikes that aligned with my theory of damping. I had to do two things: increase the gain. Without increased gain, I didn't even see FM radio forming, so I'd increase it to about `24dB` or so and then up to `49.6dB`, the max, which stabilized the signals. Second, I had to adjust the `PPM` or parts per million. This setting corrects the frequency, let's say I was tuned in to `100MHz` but the station was at `100.002 MHz`, scrolling to it which is natural, PPM corrects it globally. I didn't understand why it was so confusing, but when I adjusted the PPM to 1 in SDR++, the magic formed and I saw the full structure of the signal! Both of these options in combination allowed me to see everyting clearly in SDR++. +Initially, I couldn't just see these signals with the default settings of my SDR, I saw suspicious spikes that aligned with my theory of damping. I had to do two things: increase the gain. Without increased gain, I didn't even see FM radio forming, so I'd increase it to about `24dB` or so and then up to `49.6dB`, the max, which stabilized the signals. Second, I had to adjust the `PPM` or parts per million. This setting corrects the frequency, let's say I was tuned in to `100MHz` but the station was at `100.002 MHz`, scrolling to it which is natural, PPM corrects it globally. I didn't understand why it was so confusing, but when I adjusted the PPM to 1 in SDR++, the magic formed and I saw the full structure of the signal! Both of these options in combination allowed me to see everything clearly in SDR++. The only problem with SDR++ is that the app didn't have a rich feature set you'd think would be available modern day for SDR software. I/Q captures were poor, lacked metadata, and weren't encrypted. I couldn't get snapshots of the signal, especially SVGs, which were hard for Claude or ChatGPT to copy and reproduce in SVG. -In additon, I couldn't fix the window to be stable so I could get my signals to remain stable for a complete picture past my sample rate. So sometime in January, I decided to build an SDR web app to work with my RTL-SDR which took care of a lot of frustrations I had and folding in more features that I needed specifically for this situation, like a map of all the cell towers of places I've been (I was able to get open source data, over +30,000 towers), drawing the signal form to reproduce its mathematical form (which wasn't easy because there was no signal like it!), in addition to attempting demodulation of the signal later on. +In addition, I couldn't fix the window to be stable so I could get my signals to remain stable for a complete picture past my sample rate. So sometime in January, I decided to build an SDR web app to work with my RTL-SDR which took care of a lot of frustrations I had and folding in more features that I needed specifically for this situation, like a map of all the cell towers of places I've been (I was able to get open source data, over +30,000 towers), drawing the signal form to reproduce its mathematical form (which wasn't easy because there was no signal like it!), in addition to attempting demodulation of the signal later on. -All of this hard work under a very difficuly (economically and psychologically) and physically painful/stressful (every second, every moment, wake to sleep to walk and smoke) situation made me feel more secure in understanding the how and the path forward to escaping the NSA's horrific psychological interactive and livestream. +All of this hard work under a very difficult (economically and psychologically) and physically painful/stressful (every second, every moment, wake to sleep to walk and smoke) situation made me feel more secure in understanding the how and the path forward to escaping the NSA's horrific psychological interactive and livestream. ### The mysterious signals' structure @@ -1190,9 +1182,7 @@ It's very different from the other channels, which always have prominent spikes, These channels do not visibly adjust from changes in my physical orientation (laying on my side, standing up, turning around, etc.) or movement away from the endpoints (cell sites), nor do the spikes vary or change, as they maintain precise and exacting power at my person where they are always measured as I have to connect my SDRs to my Mac via short USB cords. Every time I look at the signals it is an eerie and powerful testament to the NSA's expertise and how far they will go in general, even in federal art-crime where you are **forced to participate (use your brain) or die ignorant (in nightmarish pain, disfigurment & isolation)**. -Given what I've observed and experienced, right brain or left brain, left or right muscles, there are no observable variances in power or structure at the outset, except minor spikes that switch from left to right. - -It's still a mystery to me how they are able to do physiological haptics and expression toward one side of the body, which I believe is possibly explained with the nervous system processing differences in sides with frequency and timing. +Given what I've observed and experienced, right brain or left brain, left or right muscles, there are no observable variances in power or structure at the outset, except minor spikes that switch from left to right. It's still a mystery to me how they are able to do physiological haptics and expression toward one side of the body, which I believe is possibly explained with the nervous system processing differences in sides with frequency and timing. ### Estimated data @@ -1241,7 +1231,7 @@ Of course, Option B was what I discovered down the line, which wasn't intuitive ### Interleaved bins -Two signals are constructed on a shared orthogonal frequency grid. The read signal occupies alternating bins, while the write signal occupies the bins between them. Both signals exist simultaneously in the transmitted waveform, but remain separable by their assigned orthogonal subcarriers. +Option B is to weave the signal as read and write. Think of it as knitting a pattern of points with two signals that make the cyber capability possible to interact with biology. READ happens first then WRITE, as one signal is READ and the other WRITE. As a signal is a series of points, when they overlap, one signal does the EVEN points as READ and the other does the ODD points as WRITE. In theory, the signals should not interfere with each other because they are orthogonal (different enough to tell apart), even though they overlap. ![Interleaved bins](/md-preview/images/interleaved-bins.svg) @@ -1302,7 +1292,7 @@ The signal is heterodyned and at a minimum requires 2 signals for the inference From experience, the NSA's technique maintains a true prison of mind and body, as the signals never fail or degrade, they are totally faultless. With this in mind, we can put aside common assumptions like noise or errors stemming from the environment or hardware, working with the signal as is. -Although what may flow through the network is perceivably small, the effacacy of the signals and its structure eludes to a fact that 0s and 1s don't exactly translate into biology, especially if you think of amplitude modulation (the signal's peaks and troughs) as a scale of 0-255. +Although what may flow through the network is perceivably small, the efficacy of the signals and its structure alludes to a fact that 0s and 1s don't exactly translate into biology, especially if you think of amplitude modulation (the signal's peaks and troughs) as a scale of 0-255. With I/Q in mind and heterodyning, this is the theoretical minimum of data that a person receives and potentially what the software may be operating on. @@ -1338,9 +1328,17 @@ With I/Q in mind and heterodyning, this is the theoretical minimum of data that +### Estimated Calories + +Given that 24/7 read and write and the flurry of effects, it's not exactly a no-cost mechanism to one's brain and nervous system. Of course, forcibly in the states of mind and body via livestream certainly consume calories. + +...TODO + ## Conclusion TODO + +## ### How long have I been trapped? ```React::DaysSince ``` @@ -1369,9 +1367,9 @@ It boils down to these 8 simple terms. - **Hardware:** The physical parts of a computer or device you can touch, like CPU, RAM, and hard drives. - **Software:** The programs and code that run on hardware to perform tasks, like operating systems, apps and firmware. -- **Telecommunications infrastructure (or tinfra):** The physical systems like cell sites (commonly known as cell towers), microwave backhaul, plane communication systems, and other infrastructure that carries and serves data over distances. +- **Telecommunications infrastructure (or infra):** The physical systems like cell sites (commonly known as cell towers), plane communication systems, and other infrastructure linked by fiber wire or microwave backhaul carries that broadcasts data over distances. - **Networking:** The links that connect devices together, either wired like Ethernet or wireless like Wi-Fi, so they can exchange data or "talk to each other". -- **Malware:** Malicious software built to exploit vulnerabilities and compromise systems to steal data, disrupt operations, or gain control. +- **Malware:** Malicious software built to exploit vulnerabilities and compromise systems to gain control, disrupt operations, or steal data. - **Hardware Trojans:** Hidden malicious changes in a chip’s circuitry or component that adds features or expands functionality to exploit existing capabilities for attacks like data theft, remote control and/or different operational modes. - **Signals:** Formatted radio waves used to carry data between points or to probe environments through timing or reflection, like in triangulation or radar. - **Cryptology:** The discipline of recognizing and constructing patterns that obscure or reveal meaning, treating any structured information as something that can be hidden or made interpretable. @@ -1481,7 +1479,7 @@ It boils down to these 8 simple terms. The NSA hacked my brain and body with the experience going from manufactured states of mind while inundated by a spatial performance then escalating to a horrifically grotesque and even more shocking livestream interactive that introduced others and an antagonistic, villanous series of narratives and crushing, tormenting states of mind and body, deeply offensive perceptions and horrific violations of conscience. -I spent years (since late September 2018 when it "spontaneously" activated to present; ongoing) trapped and went from techie to oppressed in abject poverty up against the violent and unrelenting. I spent the years enduring narrative capture: senseless violence, sexual assault and a deeply thought out, thousand-hours long script of an invasive neurotechnological reckoning/kidnapping (whose outcome is positioned for political terrorism based upon identity politics and cyber capability's novelty and mysterious nature; designed as a Darwinian horror for the everyday person through extreme pressures, nihilist suffering and extreme pain while the victim is naturally supposed to seek the interactive's completion and freedom). +I spent years (since late September 2018 when it "spontaneously" activated to present; ongoing) trapped and went from techie to oppressed in abject poverty up against the violent and unrelenting. I spent the years enduring narrative capture: senseless violence, sexual assault and a deeply thought out, thousand-hours long script of an invasive neurotechnological reckoning/kidnapping (whose outcome is positioned for political terrorism based upon identity politics and cyber capability's novelty and mysterious nature; designed as a Darwinian leftist horror for the everyday person through extreme pressures, nihilist suffering and extreme pain while the victim is naturally supposed to seek the interactive's completion and freedom). By accepting that there had to be a way out and working the problem out from its building blocks, I had come to realize how what clearly working and violating me was possible—and I was in a long string of victims in a politicized sink or swim psychological-physiological interactive and prison of mind. diff --git a/docs/pr-screenshots/housekeeping-capture.jpg b/docs/pr-screenshots/housekeeping-capture.jpg new file mode 100644 index 00000000..9b390ce4 Binary files /dev/null and b/docs/pr-screenshots/housekeeping-capture.jpg differ diff --git a/docs/pr-screenshots/housekeeping-spectrum.jpg b/docs/pr-screenshots/housekeeping-spectrum.jpg new file mode 100644 index 00000000..8632efc1 Binary files /dev/null and b/docs/pr-screenshots/housekeeping-spectrum.jpg differ diff --git a/package-lock.json b/package-lock.json index 575e4dc1..5bf5d457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,74 +9,74 @@ "version": "0.5.0", "license": "Hippocratic-3.0", "dependencies": { - "@chenglou/pretext": "0.0.8", + "@chenglou/pretext": "0.0.9", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.1", - "@google/genai": "2.10.0", + "@google/genai": "2.21.0", "@huggingface/transformers": "^4.2.0", "@inkjs/ui": "^2.0.0", - "@mui/icons-material": "9.2.0", - "@mui/material": "9.2.0", - "@radix-ui/react-accordion": "1.2.16", - "@radix-ui/react-alert-dialog": "1.1.19", - "@radix-ui/react-aspect-ratio": "1.1.11", - "@radix-ui/react-avatar": "1.2.2", - "@radix-ui/react-checkbox": "1.3.7", - "@radix-ui/react-collapsible": "1.1.16", - "@radix-ui/react-context-menu": "2.3.3", - "@radix-ui/react-dialog": "1.1.19", - "@radix-ui/react-dropdown-menu": "2.1.20", - "@radix-ui/react-hover-card": "1.1.19", - "@radix-ui/react-label": "2.1.11", - "@radix-ui/react-menubar": "1.1.20", - "@radix-ui/react-navigation-menu": "1.2.18", - "@radix-ui/react-popover": "1.1.19", - "@radix-ui/react-progress": "1.1.12", - "@radix-ui/react-radio-group": "1.4.3", - "@radix-ui/react-scroll-area": "1.2.14", - "@radix-ui/react-select": "2.3.3", - "@radix-ui/react-separator": "1.1.11", - "@radix-ui/react-slider": "1.4.3", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-switch": "1.3.3", - "@radix-ui/react-tabs": "1.1.17", - "@radix-ui/react-toggle": "1.1.14", - "@radix-ui/react-toggle-group": "1.1.15", - "@radix-ui/react-tooltip": "1.2.12", - "@react-router/node": "8.3.0", + "@mui/icons-material": "9.4.0", + "@mui/material": "9.4.0", + "@radix-ui/react-accordion": "1.2.20", + "@radix-ui/react-alert-dialog": "1.1.23", + "@radix-ui/react-aspect-ratio": "1.1.15", + "@radix-ui/react-avatar": "1.2.6", + "@radix-ui/react-checkbox": "1.3.11", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-context-menu": "2.3.7", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-dropdown-menu": "2.1.24", + "@radix-ui/react-hover-card": "1.1.23", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-menubar": "1.1.24", + "@radix-ui/react-navigation-menu": "1.2.22", + "@radix-ui/react-popover": "1.1.23", + "@radix-ui/react-progress": "1.1.16", + "@radix-ui/react-radio-group": "1.4.7", + "@radix-ui/react-scroll-area": "1.2.18", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-switch": "1.3.7", + "@radix-ui/react-tabs": "1.1.21", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-toggle-group": "1.1.19", + "@radix-ui/react-tooltip": "1.2.16", + "@react-router/node": "8.3.1", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@react-three/postprocessing": "^3.0.4", "@reduxjs/toolkit": "2.12.0", - "@xyflow/react": "12.11.2", + "@xyflow/react": "12.11.6", "animejs": "4.5.0", - "autoprefixer": "10.5.2", + "autoprefixer": "10.5.5", "canvas-confetti": "1.9.4", - "chalk": "^5.3.0", + "chalk": "6.0.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", - "csv-parse": "7.0.1", + "csv-parse": "7.0.2", "date-fns": "4.4.0", - "dexie": "4.4.4", - "dompurify": "3.4.13", + "dexie": "4.4.5", + "dompurify": "3.4.15", "dotenv": "^17.3.1", - "elkjs": "^0.11.1", + "elkjs": "0.12.0", "embla-carousel-react": "8.6.0", - "framer-motion": "12.42.2", + "framer-motion": "13.2.0", "gsap": "^3.14.2", "highlight.js": "11.12.0", "icodec": "^0.6.0", - "ink": "7.1.0", + "ink": "7.1.1", "ink-spinner": "^5.0.0", - "input-otp": "1.4.2", + "input-otp": "1.5.0", "isbot": "^5", - "katex": "0.17.0", + "katex": "0.18.7", "leaflet": "^1.9.4", "leva": "^0.10.1", "libheif-js": "^1.19.8", - "lucide-react": "1.23.0", - "marked": "18.0.5", + "lucide-react": "1.43.0", + "marked": "18.0.12", "marked-highlight": "^2.2.3", "motion": "12.42.2", "n_apt_canvas": "file:./packages/n_apt_canvas", @@ -91,16 +91,16 @@ "react-leaflet": "^5.0.0", "react-markdown": "^10.1.0", "react-redux": "9.3.0", - "react-resizable-panels": "4.12.1", + "react-resizable-panels": "4.12.4", "react-responsive-masonry": "2.7.2", - "react-router": "8.3.0", - "recharts": "3.9.2", - "redis": "6.1.0", + "react-router": "8.3.1", + "recharts": "3.10.1", + "redis": "6.2.1", "rehype-highlight": "^7.0.2", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", - "sonner": "2.0.7", - "styled-components": "6.4.3", + "sonner": "2.0.8", + "styled-components": "6.5.3", "tailwind-merge": "3.6.0", "three": "0.185.1", "unist-util-visit": "^5.1.0", @@ -110,12 +110,12 @@ }, "devDependencies": { "@ladle/react": "^5.1.1", - "@playwright/test": "1.61.1", - "@react-router/dev": "8.3.0", - "@tailwindcss/postcss": "4.3.2", - "@tailwindcss/vite": "4.3.2", + "@playwright/test": "1.63.0", + "@react-router/dev": "8.3.1", + "@tailwindcss/postcss": "4.3.3", + "@tailwindcss/vite": "4.3.3", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/dompurify": "^3.0.5", @@ -123,25 +123,24 @@ "@types/jest": "^30.0.0", "@types/katex": "^0.16.8", "@types/leaflet": "^1.9.21", - "@types/node": "26.1.1", - "@types/node-notifier": "^8.0.5", - "@types/react": "19.2.17", + "@types/node": "26.5.0", + "@types/react": "19.2.18", "@types/react-dom": "^19.2.3", "@types/react-katex": "^3.0.4", "@types/styled-components": "^5.1.36", - "@types/three": "0.185.0", + "@types/three": "0.185.4", "@types/ws": "8.18.1", "@typescript/typescript6": "6.0.2", - "@use-gpu/wgsl": "^0.19.0", - "@vitejs/plugin-react": "6.0.3", - "@vitest/browser-playwright": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@webgpu/types": "0.1.71", + "@use-gpu/wgsl": "0.20.0", + "@vitejs/plugin-react": "6.1.1", + "@vitest/browser-playwright": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@webgpu/types": "0.1.72", "agent-react-devtools": "^0.4.0", "agentation": "^3.0.1", - "archiver": "^6.0.1", + "archiver": "8.0.0", "better-sqlite3": "^12.4.1", - "concurrently": "10.0.3", + "concurrently": "10.0.5", "express": "5.2.1", "express-fileupload": "^1.5.2", "express-rate-limit": "^8.5.2", @@ -152,20 +151,19 @@ "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^30.2.0", "jest-transform-stub": "^2.0.0", - "node-notifier": "^10.0.1", - "oxfmt": "0.58.0", - "oxlint": "1.73.0", - "playwright": "1.61.1", + "oxfmt": "0.67.0", + "oxlint": "1.82.0", + "playwright": "1.63.0", "react-devtools-core": "^7.0.1", "resize-observer-polyfill": "^1.5.1", - "tailwindcss": "4.3.2", - "ts-jest": "29.4.11", - "tsx": "4.23.0", + "tailwindcss": "4.3.3", + "ts-jest": "29.4.12", + "tsx": "4.23.13", "typescript": "7.0.2", "vite": "8.2.2", "vite-plugin-glsl": "^1.5.6", - "vitest": "4.1.10", - "wgsl-test": "0.2.30" + "vitest": "5.0.0", + "wgsl-test": "0.2.34" }, "engines": { "node": ">=22.22.0" @@ -311,19 +309,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", @@ -341,28 +326,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -372,20 +335,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", @@ -417,19 +366,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", @@ -440,38 +376,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -768,23 +672,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", @@ -817,46 +704,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-syntax-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", - "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/plugin-transform-modules-commonjs": "^7.29.7", - "@babel/plugin-transform-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -922,16 +769,16 @@ } }, "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==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.10.0.tgz", + "integrity": "sha512-AOQff0zgR7cGsZL+4E7hVkmujoPUpm0J9xzWGWZj5wCjd3gmxESXAPfKyuzs93VdpQNFhHlBhfOjrcZ+XTERtQ==", "dev": true, "license": "MIT" }, "node_modules/@chenglou/pretext": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.8.tgz", - "integrity": "sha512-yqm2GMxnPI7VHcHwe84P8ZF0JK/2d2DMKPqMN+s95jQhwDMYYXKVFVJUMEaVWckQStdsjdLav/0Vu+d9YbtGxA==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@chenglou/pretext/-/pretext-0.0.9.tgz", + "integrity": "sha512-vInlpC//3vjRfWTs78k2BBIq/0bUdpjIp8TIn9fXe0NvnChhTQZfjioVzERQQbkQvCVpPpw7PDOrTWMHpq27VA==", "license": "MIT" }, "node_modules/@csstools/color-helpers": { @@ -1074,9 +921,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, "dependencies": { @@ -1683,31 +1530,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "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==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -1715,15 +1562,15 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@google/genai": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.10.0.tgz", - "integrity": "sha512-e4cFxj3tiuMtsgOT4G9c1hXyGJhg7/Buj7VVeBacRY3fRtkRZZ59Q3nuVp2xbq8BGQXLXCDB253qMhklMOeUDg==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.21.0.tgz", + "integrity": "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1795,9 +1642,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", - "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", "cpu": [ "arm64" ], @@ -1813,13 +1660,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" + "@img/sharp-libvips-darwin-arm64": "1.3.3" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", - "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", "cpu": [ "x64" ], @@ -1835,20 +1682,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" + "@img/sharp-libvips-darwin-x64": "1.3.3" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", - "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.4" }, "engines": { "node": ">=20.9.0" @@ -1858,9 +1705,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", - "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", "cpu": [ "arm64" ], @@ -1874,9 +1721,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", - "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", "cpu": [ "x64" ], @@ -1890,9 +1737,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", - "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", "cpu": [ "arm" ], @@ -1909,9 +1756,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", - "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", "cpu": [ "arm64" ], @@ -1928,9 +1775,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", - "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", "cpu": [ "ppc64" ], @@ -1947,9 +1794,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", - "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", "cpu": [ "riscv64" ], @@ -1966,9 +1813,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", - "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", "cpu": [ "s390x" ], @@ -1985,9 +1832,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", - "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", "cpu": [ "x64" ], @@ -2004,9 +1851,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", - "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", "cpu": [ "arm64" ], @@ -2023,9 +1870,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", - "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", "cpu": [ "x64" ], @@ -2042,9 +1889,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", - "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", "cpu": [ "arm" ], @@ -2063,13 +1910,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" + "@img/sharp-libvips-linux-arm": "1.3.3" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", - "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", "cpu": [ "arm64" ], @@ -2088,13 +1935,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" + "@img/sharp-libvips-linux-arm64": "1.3.3" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", - "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", "cpu": [ "ppc64" ], @@ -2113,13 +1960,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" + "@img/sharp-libvips-linux-ppc64": "1.3.3" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", - "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", "cpu": [ "riscv64" ], @@ -2138,13 +1985,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" + "@img/sharp-libvips-linux-riscv64": "1.3.3" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", - "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", "cpu": [ "s390x" ], @@ -2163,13 +2010,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" + "@img/sharp-libvips-linux-s390x": "1.3.3" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", - "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", "cpu": [ "x64" ], @@ -2188,13 +2035,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" + "@img/sharp-libvips-linux-x64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", - "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", "cpu": [ "arm64" ], @@ -2213,13 +2060,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", - "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", "cpu": [ "x64" ], @@ -2238,17 +2085,17 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", - "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.11.1" + "@emnapi/runtime": "^1.11.3" }, "engines": { "node": ">=20.9.0" @@ -2258,16 +2105,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", - "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.4" }, "engines": { "node": ">=20.9.0" @@ -2277,9 +2124,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", - "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", "cpu": [ "arm64" ], @@ -2296,9 +2143,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", - "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", "cpu": [ "ia32" ], @@ -2315,9 +2162,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", - "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", "cpu": [ "x64" ], @@ -2351,6 +2198,18 @@ "ink": ">=5" } }, + "node_modules/@inkjs/ui/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@inquirer/ansi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", @@ -2886,17 +2745,17 @@ } }, "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.1.tgz", + "integrity": "sha512-u5Ncuc+gXVUwjNMFQOnphHo2Qx2DxyC8Dvpmf4HCx4y0kvSkRRNiQYRixrvOP4V7y52JcSuNxqochCt0PpdHVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -2960,18 +2819,18 @@ } }, "node_modules/@jest/core": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", - "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.1.tgz", + "integrity": "sha512-BL9g6CJUUhIbdoAflz/Va658erSSXUIvU8XUYiWNTbZljJjZ5yaC9EJ9/dQ19rpbYV3ZOK4sBACqhPaTyhoUIA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.1", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.1", + "@jest/test-result": "30.5.1", + "@jest/transform": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", @@ -2979,20 +2838,20 @@ "exit-x": "^0.2.2", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.2", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.2", - "jest-runner": "30.4.2", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.1", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.1", + "jest-runner": "30.5.1", + "jest-runtime": "30.5.1", + "jest-snapshot": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.1", + "pretty-format": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -3007,6 +2866,19 @@ } } }, + "node_modules/@jest/core/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/core/node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3057,16 +2929,16 @@ } }, "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3122,9 +2994,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", "engines": { @@ -3132,40 +3004,40 @@ } }, "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.1.tgz", + "integrity": "sha512-eYJAkOsrpwDXPcoLNEG6lN9Zo3Cy35pxnVQD74vyIfsi/Q8wB/lZFsEjU4wrecOgT4ZviQDZaX/cFIJyMdMxTw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/fake-timers": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.4.1" + "jest-mock": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", - "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.5.1.tgz", + "integrity": "sha512-J395vmP3Fb2Te0JmF7pe4si4jpfbXef1YsY4UpHYL6OOxS2molu9Dsie1VmIiUalXdtmz1P5QRgc+5hBD+ssBg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/jsdom": "^21.1.7", + "@jest/environment": "30.5.1", + "@jest/fake-timers": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "jest-mock": "30.5.1", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { + "@types/jsdom": "*", "canvas": "^3.0.0", "jsdom": "*" }, @@ -3176,54 +3048,54 @@ } }, "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.1.tgz", + "integrity": "sha512-uOGd40P/COyUp9xHf5jeGiGJC2/ANg+2+Tk9/xN5/LxmlY/r/gxsPrx3DTEtaRZsLAX4wgNSLEDELZ5bmVs2bA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" + "expect": "30.5.1", + "jest-snapshot": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.1.tgz", + "integrity": "sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/get-type": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.1.tgz", + "integrity": "sha512-rEkV6YzpBXo/L9cnj2ibyuYZuyGiNWaPUsyCu3HYJbqCV4jnEiKmf7hId20z8eKjZ0JQ9jtIYKxRSmYB/OYSsA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "jest-message-util": "30.5.1", + "jest-mock": "30.5.1", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", "engines": { @@ -3231,62 +3103,78 @@ } }, "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.1.tgz", + "integrity": "sha512-VhqvQ251XIC7pk46YymI3HCeBLOdvriDw9zibekodAHEwoVvbVVgpU5H13GvmyOAzxtZCfsm1uvzqkaqJf2I+g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" + "@jest/environment": "30.5.1", + "@jest/expect": "30.5.1", + "@jest/types": "30.5.1", + "jest-mock": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.4.0" + "jest-regex-util": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.1.tgz", + "integrity": "sha512-RbUXIfv85KxitJn4l3MpAoMilkvXe2QCO5lXxHWftywo/VdZ5vkEoHRDgphcxP6qmoIkUaN8/UZj6NhdkIFdJg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", + "@jest/console": "30.5.1", + "@jest/test-result": "30.5.1", + "@jest/transform": "30.5.1", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -3380,13 +3268,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -3442,29 +3330,37 @@ } }, "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.0.tgz", + "integrity": "sha512-xWpTJP9D0bDFGbPGT8XuWSwwha/iHADyyKzUnMx4UbdgnHugxrDaQFO4RZ8x4ZsFzRP6pNii8uvlgKCDxCIuDg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.31", "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", "graceful-fs": "^4.2.11" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/source-map/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.1.tgz", + "integrity": "sha512-A/1S6ZBdpic50E0pxLgvaB9XNPL4k7AksmG69OO2oiotxciWZwHkbOec+qbIi+uUtIIByOVlXJUl97oZUsz+Jw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.1", + "@jest/types": "30.5.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -3473,15 +3369,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.1.tgz", + "integrity": "sha512-SHcPnrjdVRYJv6y6l4JUTy4Jccu7zmO6BmiOfOA34UiVBto22s19WiDC0A/2qfM7MWB/qdcoqfSIRMitpjTT4A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", + "@jest/test-result": "30.5.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", + "jest-haste-map": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -3499,23 +3395,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.1.tgz", + "integrity": "sha512-EDnDhn0jleU9ZhpVoA4gvqw+Ev0iw/r5upNT2b79RiwiaTiYAMMhvNJP3lmocjIe5J2ZkoNr0B3K/J6O5GIm4Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -3588,14 +3484,14 @@ } }, "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -3606,6 +3502,19 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/types/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/types/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4514,9 +4423,9 @@ "license": "MIT" }, "node_modules/@mui/core-downloads-tracker": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.2.0.tgz", - "integrity": "sha512-+XMav+ZaXkZKUFUgzjrfMEedfyJKxxviAske2q8N8CWDMeqZdDU2lWMkiUPiB388hGaDqhwvOAwkrsc/pUyp8g==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.4.0.tgz", + "integrity": "sha512-d+gIYM1raJP87yrVQcVg+Q3vycVMArMkgu/TjJLi2vTvO9FHKWNRR1xs9nnN4eLpM9nGxxm02UMvc9BrmcUKvQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -4524,12 +4433,12 @@ } }, "node_modules/@mui/icons-material": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.2.0.tgz", - "integrity": "sha512-VgBd3z7Qc3vd/thcNSMC03nHRh/U4DzMUd+1dRyJTbm/hGo7+N6N4GDuJZDNHa6LZhhwG6Cu1X3DNvrVv8sNag==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.4.0.tgz", + "integrity": "sha512-5PVgBYtLOXTk6u0YUAjoV9GoTUQDbeZ8g+pus2h0GphLT/rpmftRnB5D/Kw6QCAovB7EyBX7UxKdZqPBBAHUKw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2" + "@babel/runtime": "^7.29.7" }, "engines": { "node": ">=14.0.0" @@ -4539,7 +4448,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^9.2.0", + "@mui/material": "^9.4.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -4550,22 +4459,22 @@ } }, "node_modules/@mui/material": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.2.0.tgz", - "integrity": "sha512-+YTRSgGKGrrRo2XJZXs7JRA6qHoHWvNtxyqxnrRJTBmIuLOUpxxh7m4G9lF4tWberxGFY+EqkkRPgJCl+fSMJg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.4.0.tgz", + "integrity": "sha512-6KKUIvkQ2r/s48s9VZV9JEZ1W2dRa7RndVsF+Ipx4CYxDOQeozeRsS5r9NiheFf8A5hpbqJJIGoX7hjg5X4XUw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", - "@mui/core-downloads-tracker": "^9.2.0", - "@mui/system": "^9.2.0", - "@mui/types": "^9.1.1", - "@mui/utils": "^9.2.0", + "@babel/runtime": "^7.29.7", + "@mui/core-downloads-tracker": "^9.4.0", + "@mui/system": "^9.4.0", + "@mui/types": "^9.4.0", + "@mui/utils": "^9.4.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1", - "react-is": "^19.2.6", + "react-is": "^19.2.8", "react-transition-group": "^4.4.5" }, "engines": { @@ -4578,7 +4487,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^9.2.0", + "@mui/material-pigment-css": "^9.4.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4599,13 +4508,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.2.0.tgz", - "integrity": "sha512-w9wpyDxGPGnAACPB2hKhCDmILJIAvQxrfjUbIAEa0AznX1rOjaz5N+yB1uuw8ixnJcpEh/tPbD9oEe19wcWPHw==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.4.0.tgz", + "integrity": "sha512-qQpX/2XPGDz5F2OIWzo4kkorURlDeXGKytRQudXCdLS5TuwKsMKaZyx77LZEVSB73c7o3UrLWwQ/goRuHBdtjw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", - "@mui/utils": "^9.2.0", + "@babel/runtime": "^7.29.7", + "@mui/utils": "^9.4.0", "prop-types": "^15.8.1" }, "engines": { @@ -4626,12 +4535,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.1.1.tgz", - "integrity": "sha512-neaYKdJfvEG54q8efHLJR7swpHG/gfSv9xGqW5iTSMsubD7yPCPFrhVBt284j1DOF3uZaaDJSHQL7gz6jGF21Q==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.4.0.tgz", + "integrity": "sha512-SlqVEYqnyEgXMFW4JuIDMelyhKzoWBJ85v2mNCQd0XtU0uk7U9E1zaJZ06XqodhA8oJrb+4Q+3E/JlwBe4jWkw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", + "@babel/runtime": "^7.29.7", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", @@ -4660,16 +4569,16 @@ } }, "node_modules/@mui/system": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.2.0.tgz", - "integrity": "sha512-YvUJwKoGVtbnOm2PyPi5TvX2d1rOA6sqSpEWVs4WmXNIaFTuYmNUaVdU2o1NKUEe31URnD3E8ZVUMcsLQXwcYg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.4.0.tgz", + "integrity": "sha512-AZz9z13oGS1J0BYeAFHTbV7PegCtPiE/uMEIi7TJ1kTx3Vkt+jUpLyXF/2uV8B6Y79iTh98kVeOH9fX3x3zXXg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", - "@mui/private-theming": "^9.2.0", - "@mui/styled-engine": "^9.1.1", - "@mui/types": "^9.1.1", - "@mui/utils": "^9.2.0", + "@babel/runtime": "^7.29.7", + "@mui/private-theming": "^9.4.0", + "@mui/styled-engine": "^9.4.0", + "@mui/types": "^9.4.0", + "@mui/utils": "^9.4.0", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -4700,12 +4609,12 @@ } }, "node_modules/@mui/types": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.1.1.tgz", - "integrity": "sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.4.0.tgz", + "integrity": "sha512-13DH0Oniua4WmGqeYbQAZqmiN7r4nfd0zwIoGJ5QeJwyIHgEmf+LkRw/jV1HZb6AiUoHX8Oxf4xgalq3vYHcIw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2" + "@babel/runtime": "^7.29.7" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4717,17 +4626,17 @@ } }, "node_modules/@mui/utils": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.2.0.tgz", - "integrity": "sha512-OsUH5zhlSOM4xmLl53+agug1M1UyWb4zxFxWQCqwKTKUeQPvTENtg3JhrroBD2qpCLKsX5W/DYGERJ4mBUbc8g==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.4.0.tgz", + "integrity": "sha512-WkzY8uYtqUDyGKPShrRQRBomfMQHddlsyKu/gRRESYO24tDPD2z0xRE8CBoUsWqpBa+YrFs9KixiS7kQRjsPAQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.29.2", - "@mui/types": "^9.1.1", + "@babel/runtime": "^7.29.7", + "@mui/types": "^9.4.0", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.2.6" + "react-is": "^19.2.8" }, "engines": { "node": ">=14.0.0" @@ -4747,22 +4656,25 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@nodelib/fs.scandir": { @@ -4839,9 +4751,9 @@ } }, "node_modules/@oxfmt/binding-android-arm-eabi": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.58.0.tgz", - "integrity": "sha512-Uz62sHduGGPftXtILGyxdSW4PX82rUg+rfdNqhsgxe881g4rIoXlIqmZQ6HVKcF4f+F8qMhdD03Bx5u7gmeTdg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.67.0.tgz", + "integrity": "sha512-2olh3ioEmc4gRzQm7jxyB1b/PFBoFvTq8KdgYySeNpysDtA6DEg2Mvya4/I6flhL7G0eOrE8RD7JCNCIMhE16Q==", "cpu": [ "arm" ], @@ -4856,9 +4768,9 @@ } }, "node_modules/@oxfmt/binding-android-arm64": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.58.0.tgz", - "integrity": "sha512-rD0lRaJp1b+9vw6X4A2dJWKukd6X8yxiicN4JxXcXayolmUypRZxk+lKR+fVOu5q/iYc0fh5fR4bgmfOfVlbaA==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.67.0.tgz", + "integrity": "sha512-ulfw8EHN1MBq/MFFDXw2/M1VAFu5mRUcnuZ8Hqbv9viAnFzO9t1jKSAsDqKYYDGMlytF/uj6Z5z5n/tHupnKhw==", "cpu": [ "arm64" ], @@ -4873,9 +4785,9 @@ } }, "node_modules/@oxfmt/binding-darwin-arm64": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.58.0.tgz", - "integrity": "sha512-uzbPPk7O6M+w2K65vcQ1woga3wgP8zghjL1KOG5b6qJ8dvYHZJ1VShaslg2KOK6yQIwCQtcMCXqLBM6sqXUNTg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.67.0.tgz", + "integrity": "sha512-MfONZx/O2o9M5v2jDFol556G9+A+P9xCuJ4DZ+qhE+RnaCdoscy6Eu5nq1dbuNxhwdJyZ6kLI7fnG9mwEeOeGg==", "cpu": [ "arm64" ], @@ -4890,9 +4802,9 @@ } }, "node_modules/@oxfmt/binding-darwin-x64": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.58.0.tgz", - "integrity": "sha512-L0nKYDxU32oxeQqJj21W9SlIMnf81VZEhyah6iDvFhf5q0oynq498Fopth7blErUJVBpVtxQ98RMCfMPqpJX6w==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.67.0.tgz", + "integrity": "sha512-CYnIx5LvFVJnyJcCqwH2jxMKjFjqo5678MPjdmNFoSGMhlOvZ/xRZqvhDcolKrXc8fezW3AKh+C4wyoFuWOSSg==", "cpu": [ "x64" ], @@ -4907,9 +4819,9 @@ } }, "node_modules/@oxfmt/binding-freebsd-x64": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.58.0.tgz", - "integrity": "sha512-woNwfD58dC5PGS9LSLSD5JYfo/EFK5iG9vhDWkcCg3q78ag7KC8bpDqgvPHrMoXpx83OLXxoSOhu6z8FsVTHlg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.67.0.tgz", + "integrity": "sha512-7/iF1orvIS9mxhKUqnmtMgm+OrSQ5acPwuvdQrm6ECgqbwPmC+Pw9cdke3sNfVN6pT2hbJ58+jP8BCThl5HXOg==", "cpu": [ "x64" ], @@ -4924,9 +4836,9 @@ } }, "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.58.0.tgz", - "integrity": "sha512-Sqs8nMLxuQpY21NKJ1u4stPDmO5hskBCNNh2E3AdCfI1QqWtf4m+Qn4mGEIUO4KGmuq3SWc/SZ80uy5IiwTCDw==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.67.0.tgz", + "integrity": "sha512-yy+OGys07IZOpOmYPZoObKyUQLkfxeQqeCypk+1jaZd8HGo77hzvU1Jg8X3+W75o+9lszOjBfg0nkGtlwYywXw==", "cpu": [ "arm" ], @@ -4941,9 +4853,9 @@ } }, "node_modules/@oxfmt/binding-linux-arm-musleabihf": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.58.0.tgz", - "integrity": "sha512-Vd4exzBI5B5hB9m22JiTQzIL23WvHo/Pe+sNXPNeBLXSP9swCBPKCEBRwKpmpQzYhlgYaCgfPcGXPKAJBRIiZQ==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.67.0.tgz", + "integrity": "sha512-wPIeeigXgJpwNw3wydYRt3U9iN9Y/ejpOZuYL9IA7igxWs7LIQMOkhKxTumRvy6dIv0iXKk3RTw3Vmjg0i+2sg==", "cpu": [ "arm" ], @@ -4958,9 +4870,9 @@ } }, "node_modules/@oxfmt/binding-linux-arm64-gnu": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.58.0.tgz", - "integrity": "sha512-bUWi5mHV+4Vi56RLHE1h6q/HHfwAIT3XoB9vJAVeRzfu5NriXM8y6eeJu0vlKa0C9kq2rq1sOWRClhdLHPocrg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.67.0.tgz", + "integrity": "sha512-0+XNxcdbkTfxdcD4qW6Ci9n+mBNJ8xTBumnxKvKBmRFOdx0Wf8/KiHjCJayooXmYkqRpRVd98Q5egvzx5BLSgQ==", "cpu": [ "arm64" ], @@ -4978,9 +4890,9 @@ } }, "node_modules/@oxfmt/binding-linux-arm64-musl": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.58.0.tgz", - "integrity": "sha512-2ZHxemzgHcjtktAuVUwSoyXmGo/t+aF5tS1ciPpPei4rhSyrz3JOqDosXXrmhN/yLUSzJjtuW7ToTWqfQpCj2w==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.67.0.tgz", + "integrity": "sha512-I75LKPJyNOYUzkqAiAMIE31+Ye7xtQXZdoty1IXn4B+bw5Zpmez5wfG19ejGpNnS/BzQ7LFS+7jxuTPb+vHiZw==", "cpu": [ "arm64" ], @@ -4998,9 +4910,9 @@ } }, "node_modules/@oxfmt/binding-linux-ppc64-gnu": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.58.0.tgz", - "integrity": "sha512-AwKkVwjVmFQ3bcO7j0McGYAqCKH2a326fswfofng/E8VewCT/raeeGQr4huVhY704deK8AWASSTlxzMj0eZc6Q==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.67.0.tgz", + "integrity": "sha512-c2M5iRpe1QMZSRE/UvZoPdXBWb5Ic/ycvOyNiKCqPwQ/OyOKIMiJs02ynlNnjb7ZZJnRXYLmGcohoINOcwDK3w==", "cpu": [ "ppc64" ], @@ -5018,9 +4930,9 @@ } }, "node_modules/@oxfmt/binding-linux-riscv64-gnu": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.58.0.tgz", - "integrity": "sha512-xsRpTxfUnJF8D3AUKko/qyWdjw4GZVHlCVFuGlzSCTeewLmykKINW8em1+wx+axsDVtJJcMtvsiaXggXxrlHgw==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.67.0.tgz", + "integrity": "sha512-dQzzYlV24Udhfm5ECuSdgqRvFJU/CGHzcYYEO3dLM6W6+CHiBFrq9OjIllkdCcPhsoSQ8o223Dja84MOSzed9A==", "cpu": [ "riscv64" ], @@ -5038,9 +4950,9 @@ } }, "node_modules/@oxfmt/binding-linux-riscv64-musl": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.58.0.tgz", - "integrity": "sha512-Z4AYOTcy7nYEIiXwD62PlerimyYRcfJOgUbQAEBjXz098kxKuERBlRntofGy69HHhe9E0TLVNMl1yspVNu+efw==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.67.0.tgz", + "integrity": "sha512-rFNq1CgX4qMJANOq42LkAs90JE80GpiaEohAV2qn/gT2hGjQTW1zBO5zQBxArI4926pM1OSzo3CN0tBszGBIaA==", "cpu": [ "riscv64" ], @@ -5058,9 +4970,9 @@ } }, "node_modules/@oxfmt/binding-linux-s390x-gnu": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.58.0.tgz", - "integrity": "sha512-A3nhhtZPC/TKVWOPj9q/H3p2znJDCcHWYlJBhWL8hGq/bFmBaNBHC8Np6E581yVq1w9Mi3rMDNzDalWvtUfJtQ==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.67.0.tgz", + "integrity": "sha512-Sky6rEdz2o5IGq01lPhS12yEvDdChVEcaYrcLHkveh4Fx0qPjljE/Iul6SX/bRMl6lNc8J7J/mDQdzgBdA++Pg==", "cpu": [ "s390x" ], @@ -5078,9 +4990,9 @@ } }, "node_modules/@oxfmt/binding-linux-x64-gnu": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.58.0.tgz", - "integrity": "sha512-2g+tVkgwqphw8R4hgo+kF4oz8+P5RwVOtr9+irsC7uwEp0e9j7Crw8kDGKL20uYlLPD7g02DqA61mC/UNYx98A==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.67.0.tgz", + "integrity": "sha512-vPXmlNORV8AZq2Ocxh07pxwMjfENUWCV/eZArnao0qC3NO/hDeTVkQvee7SJJUbIiF5PZbBa4kYmaXnu7Rk58w==", "cpu": [ "x64" ], @@ -5098,9 +5010,9 @@ } }, "node_modules/@oxfmt/binding-linux-x64-musl": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.58.0.tgz", - "integrity": "sha512-rc15P6AbyyB7426aN8AakLd02Trb3a6ML/mmfAQeVHJEfVofWLcWIrBdy6zDEY+DIaL/s8E4GGPboVw+oP3+EA==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.67.0.tgz", + "integrity": "sha512-x/WAtFqYtVr3vZ9ni8nr4kn9whSitg8fOljq/pZzBpxopRdY1BMLZCZkrbIbaBcYkm46qGbqVea2FCWmtQ2P9w==", "cpu": [ "x64" ], @@ -5118,9 +5030,9 @@ } }, "node_modules/@oxfmt/binding-openharmony-arm64": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.58.0.tgz", - "integrity": "sha512-ZWoTM27/HYPOh9iq86DAbhPu9nXb8qKvvGU/h8OfliyVUFAMMNTLDkGsWDKKnDqIkqvZ9+dXlgUOsH1LYO3O7g==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.67.0.tgz", + "integrity": "sha512-eRw9Neh4/aA6i+q/R3WU1gGQINhVM0J4fXIm6t27caOamkr/37uAkp1IdBx4zlJH97hmXR63z/q9n5c5dN7MzA==", "cpu": [ "arm64" ], @@ -5135,9 +5047,9 @@ } }, "node_modules/@oxfmt/binding-win32-arm64-msvc": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.58.0.tgz", - "integrity": "sha512-LHZnqFXe2dEfkRI4XdZS/57nEOT/I4UCRX5IyM9v4GYW9XwQCjGe1IUK59SuKw3POwvcgWQ4pme2cYXmNqTNPg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.67.0.tgz", + "integrity": "sha512-YIMvb+sGNYN2uc6+QK2HLPeEKM2vl7QZ5onQzpAJRb6pnf0DwUFP5R8tdS9R0l8hdUil2gu4Uxd0Yxrop0iT4w==", "cpu": [ "arm64" ], @@ -5152,9 +5064,9 @@ } }, "node_modules/@oxfmt/binding-win32-ia32-msvc": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.58.0.tgz", - "integrity": "sha512-mZKpg20TpheCJym1rarcZCUJeW1sSruw8zAAaCYWvuVfwIUDN1CXdrPU/JgCWReXTCTrEfCB8Wyo3hh9jSZ2EA==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.67.0.tgz", + "integrity": "sha512-LzmU9MyACPzwNDIK0ItMedHPz735Ug7ELWguxo4/kuy6zWuDoeglOAEFCY8jLg0PzRpFO3hDyLFe2Gu2eFDeGA==", "cpu": [ "ia32" ], @@ -5169,9 +5081,9 @@ } }, "node_modules/@oxfmt/binding-win32-x64-msvc": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.58.0.tgz", - "integrity": "sha512-N/wUU4N5PZ2orBtI+Ko7MnMfYLfE7K91UrGMY/c/pYyHR3lA9kwst1XugkZx+92YcRh/Eo+iv2eTESSWXfiZPA==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.67.0.tgz", + "integrity": "sha512-sbQOIDNLUEeVZcAJcSL5VURn7kfjvilPviody4Yl5n8lQCDtUm+C9oHTTwZS/m4d/Z6Vv3jNEiAofH932NPPCg==", "cpu": [ "x64" ], @@ -5186,9 +5098,9 @@ } }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.73.0.tgz", - "integrity": "sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.82.0.tgz", + "integrity": "sha512-a3LB+C5Dsj5b/qtmG/mv5WrzuiXEpg1KF5nXWcEvaoN5TYAqkIvxPOwTPp3Jy/FoGpRo8zsTFhMElMXfeoOEzA==", "cpu": [ "arm" ], @@ -5203,9 +5115,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.73.0.tgz", - "integrity": "sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.82.0.tgz", + "integrity": "sha512-OBlhRgNqFblGpGenno/aqOfJLOkQ2B8Ig3iDAalfn0H8hJGZKXPeexCRTDm6uwv6YUjSA9Xnwt1y/Bgj5ZH8uw==", "cpu": [ "arm64" ], @@ -5220,9 +5132,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.73.0.tgz", - "integrity": "sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.82.0.tgz", + "integrity": "sha512-dsopxqtY5ZdyT9uLHyGt1SyiLop6hi7hWI3PKpePodkRQOkLaCm+OE4fR9CAz9qdfjiFO8531tX/QDyP/psjFg==", "cpu": [ "arm64" ], @@ -5237,9 +5149,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.73.0.tgz", - "integrity": "sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.82.0.tgz", + "integrity": "sha512-94Lu0SgTClKColU66g1VDuigV3HkcbkJBnTtZjGYfE8UPugaWDgKrm2icjC6HJVUYler2OXaHP/X0TBy8+CowQ==", "cpu": [ "x64" ], @@ -5254,9 +5166,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.73.0.tgz", - "integrity": "sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.82.0.tgz", + "integrity": "sha512-hne/V06ewhh1i0w8+l7GDNROAGCGPmyFuOwiP7YTRu0JycyStJ4785dmF8xU5p0uUwt2emvIF9vc7Xjis+cJ0g==", "cpu": [ "x64" ], @@ -5271,9 +5183,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.73.0.tgz", - "integrity": "sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.82.0.tgz", + "integrity": "sha512-aWY2xtbZf1LneW9Qsv/n2Sp8gOu74JrlQzEtj4coHX2SHFrCfhmAumaU+sI/A5nr+yoTRTSmI/pL2s6ADlNSkw==", "cpu": [ "arm" ], @@ -5288,9 +5200,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.73.0.tgz", - "integrity": "sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.82.0.tgz", + "integrity": "sha512-Fe+TtXCXMh/5f7kWlZ2VAwsMumZWtraFlKVk1NJlL52/beGwfDE7ov+/8gVirHzWokzGu7X65hSPq0ucPDskWQ==", "cpu": [ "arm" ], @@ -5305,9 +5217,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.73.0.tgz", - "integrity": "sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.82.0.tgz", + "integrity": "sha512-6azCZ6OJudlvipNttXCCQcyeFfcJ/NvUZdSN1z8elo73kCHtyQC7WTiUcSjWYvJ1jaq9KDUyMAoAS/vNzhBomA==", "cpu": [ "arm64" ], @@ -5325,9 +5237,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.73.0.tgz", - "integrity": "sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.82.0.tgz", + "integrity": "sha512-PLEaSD8IAIIlwW4dwOd9YaxuxeOpwiXL4J24rcnE4iNtyM5j9Q9/3+gti08oXpx0u2ygNjRDx9xjWWpQonuJEw==", "cpu": [ "arm64" ], @@ -5345,9 +5257,9 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.73.0.tgz", - "integrity": "sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.82.0.tgz", + "integrity": "sha512-D94em/BwknNTn4vqxjHh5wb2oL566eFhArabqKIr0cNZMHOJuiraFp1A8tXpH05bbE5tqwEfLXTI0MWEGtn3Dw==", "cpu": [ "ppc64" ], @@ -5365,9 +5277,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.73.0.tgz", - "integrity": "sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.82.0.tgz", + "integrity": "sha512-MOprxBaoYU2D4VgxXCl3ghydThWtx7Um1lL51kGYNeQ5Al7WzsH7/tqGdNtbLrIWnjq3bsm13+nz/gRIxjrOXw==", "cpu": [ "riscv64" ], @@ -5385,9 +5297,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.73.0.tgz", - "integrity": "sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.82.0.tgz", + "integrity": "sha512-5h55QsfJ/luDXZzC20k6SNOY1Az+dCP9WvntKtcUWh2JhckAdwApY2ZusaBTwLENnReXU+A2fJtSrYvZJNKNPg==", "cpu": [ "riscv64" ], @@ -5405,9 +5317,9 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.73.0.tgz", - "integrity": "sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.82.0.tgz", + "integrity": "sha512-IE8NJNLlHr0CaXyGJPGVn0eTkUyoj1I2UfA8x7I4PSOYKsQ/6btVC7Pywrj5onk0cMH25r6Z38SoN3AvE5Zuog==", "cpu": [ "s390x" ], @@ -5425,9 +5337,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.73.0.tgz", - "integrity": "sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.82.0.tgz", + "integrity": "sha512-XUUUxaBo9XKl+J1B9EmP1cTGQPddzeURvoGkfwh/94PGnbW+hBprDljneoI2M1jzC1bzrIV3ihc7iM9UXl8+tg==", "cpu": [ "x64" ], @@ -5445,9 +5357,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.73.0.tgz", - "integrity": "sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.82.0.tgz", + "integrity": "sha512-SWLSFulX9TDuH6yvbPYp4+VNn6jkkIvvI+KiujDM5rWBRHEfkesCC/pCneIIUr6ovkxZ5fRtpi2v5Cz5FrMJZg==", "cpu": [ "x64" ], @@ -5465,9 +5377,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.73.0.tgz", - "integrity": "sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.82.0.tgz", + "integrity": "sha512-BQy35f6ZUdNr9a6c7B7orxQTcLjByGT2z3WAgmRovpRwmPYAaJ+NTplmMzhdjdJ4qSchfMNZy/Ukg+qRg6zseQ==", "cpu": [ "arm64" ], @@ -5482,9 +5394,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.73.0.tgz", - "integrity": "sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.82.0.tgz", + "integrity": "sha512-V4QhSTg5gctZue8RJjsGi7NpQPThr/p1/HfmiMC5kfe1KFEup9SQRVub4A6kijQjdHfxj7bLL1KO3QO7/5bwMQ==", "cpu": [ "arm64" ], @@ -5499,9 +5411,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.73.0.tgz", - "integrity": "sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.82.0.tgz", + "integrity": "sha512-TUSCLaKB2yktpFAJ/r3HAUYsaV/3DT7JS4iNKyoh3a9YNwD0UG7Ezh4D8m23654vQcU6P/RQrCAjRPKe4peP/A==", "cpu": [ "ia32" ], @@ -5516,9 +5428,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.73.0.tgz", - "integrity": "sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.82.0.tgz", + "integrity": "sha512-VTVoRIWJTb+wvUX8EYoPArfFH02whuR10goFXE/LHRRX33ajRrFgqbcONXZMiF4C5rnattfkm87HqYn8jb8hmQ==", "cpu": [ "x64" ], @@ -5532,51 +5444,356 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@petamoriken/float16": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", - "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/pkgr" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@playwright/test": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", - "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", - "dev": true, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "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": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.61.1" + "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@polka/url": { @@ -5654,32 +5871,32 @@ "license": "BSD-3-Clause" }, "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "license": "MIT" }, "node_modules/@radix-ui/react-accordion": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.16.tgz", - "integrity": "sha512-BpZJNmetujnGgUI6OX0jEhEmlA46WPqgub8Rv09Kyquwd0cc1ndMKpiPYCjmBU6KSSRPAMtgLpEoZSG/tdNIWQ==", + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collapsible": "1.1.16", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -5697,16 +5914,16 @@ } }, "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.19.tgz", - "integrity": "sha512-FA7n1f6D/DwGE0+AWxiY5LacNbbExQuEgMubeG06idEaH+mSLuf9dp/qBNqOnvbTQ+4gZ2ue1RATF1Ub91Mg5g==", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dialog": "1.1.19", - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -5724,12 +5941,12 @@ } }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.11.tgz", - "integrity": "sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -5747,12 +5964,12 @@ } }, "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.11.tgz", - "integrity": "sha512-IUAhIVpBUvP5NNICjlaB1OFmtRLGqQqTF3ZOSGPoq3XeLXRFtHiWTRxSVEULgOd9GQR2c7tsYqDnhUennapZnw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz", + "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -5770,16 +5987,17 @@ } }, "node_modules/@radix-ui/react-avatar": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.2.tgz", - "integrity": "sha512-sST0qh8GzOB7besQ3tMLWLyngnRuSk0gc/Hm+667KYKQFCt6Y6ZXv25WlqM7dIDK54ULCh5+CHmk4LIolzfz+A==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz", + "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5797,19 +6015,18 @@ } }, "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.7.tgz", - "integrity": "sha512-JroKHfQBfh+fDuzpPsBC+pESkhuq8ql4hljTguz8MWnS35cISr3d/Jhl9kYrB44FlDtxCArYdDvTx+BSsJ64rQ==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5827,19 +6044,19 @@ } }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.16.tgz", - "integrity": "sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -5857,15 +6074,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -5883,9 +6100,9 @@ } }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5898,9 +6115,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5913,16 +6130,16 @@ } }, "node_modules/@radix-ui/react-context-menu": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.3.tgz", - "integrity": "sha512-PS+gKE0z2prJ74Y0sM+brAGK4mYOHIR7TlcV5EJgUQ6E0xMvyswkK2X4yRqyganrzsRL+WCSKAPu0NQITICRWg==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz", + "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -5940,23 +6157,24 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", - "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -5976,9 +6194,9 @@ } }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5991,16 +6209,16 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -6018,18 +6236,18 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz", - "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6047,9 +6265,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6062,14 +6280,14 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6087,20 +6305,20 @@ } }, "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.19.tgz", - "integrity": "sha512-2KTgMLQtKvicznQgbindEI2RZ3QbDIwU5gabjUPwFJsormjGDz+rUvO4NANmYwzEEpTcTONUt33vBHIfTIVSfw==", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6118,12 +6336,12 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6136,12 +6354,12 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.11.tgz", - "integrity": "sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==", + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -6159,27 +6377,27 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz", - "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -6199,21 +6417,21 @@ } }, "node_modules/@radix-ui/react-menubar": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.20.tgz", - "integrity": "sha512-gzFZvybgmwYsFBWDqanycIoEYnhyk8MMnuLamdFVHUZYGp4COM+sqXiwbnn0VMWqGLeeU7GV7jm+dXRa+Wufag==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.20", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3" + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz", + "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6231,25 +6449,25 @@ } }, "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.18.tgz", - "integrity": "sha512-K9HiuxZ6xCwSaHcIuUpxyhy4w5gpwzWjh9dHTSbMN3Ix4qAyVObS9RlU3zMycb0PO3v9Tpk0BXMwWvXOUbVXew==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7" + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -6267,24 +6485,24 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.19.tgz", - "integrity": "sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -6304,21 +6522,21 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", - "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -6336,13 +6554,13 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", - "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6360,12 +6578,12 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6383,12 +6601,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", - "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.3.0" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -6406,13 +6624,13 @@ } }, "node_modules/@radix-ui/react-progress": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.12.tgz", - "integrity": "sha512-ZPHyI0JyzoH/rP0tq2uRaIZTj/4s8+kAbqPz+e2N8+ejHvwPJ889dHhqn+vh7PNvNeq+boAoH9yzqeoShzwF2w==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz", + "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -6430,21 +6648,20 @@ } }, "node_modules/@radix-ui/react-radio-group": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.3.tgz", - "integrity": "sha512-WwZFjWV4s3aC1QtR3k04R+oANHtX2q6fgKlc7MCEiDNlnTxCZ3H8k3mHtEgVlOejystwk1WQgarQhNOQZ2bK1g==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6462,22 +6679,22 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", - "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6495,20 +6712,20 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.14.tgz", - "integrity": "sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6526,31 +6743,31 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz", - "integrity": "sha512-L5RQTXz6Anxsf9CCv+pTgiAsUpyVj7rJxsGtmhFaEOJ++cVfXucv4qWfsIO0AIB4NAhi3yovWGVMKKS1Xf1Wrg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.7", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -6570,12 +6787,12 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", - "integrity": "sha512-jRhe86+8PF7VZ1u14eOWVOuh2BuAhALg/FT1VcMC4OHedMTRUazDnDlKTt+yxo5cRNKHMfmvZ4sSQtWDeMV4CQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz", + "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -6593,22 +6810,22 @@ } }, "node_modules/@radix-ui/react-slider": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.3.tgz", - "integrity": "sha512-CWVVj+XaTom0SKCqw1EUgb0NuiLwS+N3OFG73mVEezKEjgNIvZiu0EevMelSSU+CbX3owbqJweG2gPU31WGC5A==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.2" + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6626,12 +6843,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -6644,18 +6861,17 @@ } }, "node_modules/@radix-ui/react-switch": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.3.tgz", - "integrity": "sha512-1+mlB4/lxJfk5tgJ4g+R5mUCbRpPE1T9+UsEyeLYbGgMtwiMgmuTnfKz4Mw1nHALHjuwyxw4MLd4cSHn6pNSlQ==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-use-size": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6673,19 +6889,19 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", - "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6703,14 +6919,14 @@ } }, "node_modules/@radix-ui/react-toggle": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.14.tgz", - "integrity": "sha512-QI/hB65XKWACA66P64A+aHxtLUgHJeJLkaQa+awUNXT6T3swndtY5DojeHA+vldrTspMTtFBd7HfZ9QGbM1Qrw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz", + "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6728,18 +6944,18 @@ } }, "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.15.tgz", - "integrity": "sha512-gIC5Q+Xljg7lmUdzSuDoy0t97yZn1sZl00Ra37ZvKrYdWnQLU6sWLd09yG8cIB9jUAlQfHgJ2ACAG00MFwsqSQ==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz", + "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-toggle": "1.1.14", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -6757,23 +6973,24 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz", - "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-dismissable-layer": "1.1.15", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.3", - "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-visually-hidden": "1.2.7" + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -6791,9 +7008,9 @@ } }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6806,13 +7023,14 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6825,12 +7043,12 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6843,9 +7061,9 @@ } }, "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", - "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6858,9 +7076,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6873,9 +7091,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", - "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6888,12 +7106,12 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.2" + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -6906,12 +7124,12 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -6924,12 +7142,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.7.tgz", - "integrity": "sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.7" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -6947,9 +7165,9 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "license": "MIT" }, "node_modules/@react-leaflet/core": { @@ -6964,21 +7182,19 @@ } }, "node_modules/@react-router/dev": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-8.3.0.tgz", - "integrity": "sha512-XR+N2fEFOPjczYo2efc3/AOtosbSICCroLF/IxnZ6ErGBeBGRG6SqAso0SYoff0e18OA05qOyhJHFhXKMGIPRw==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-8.3.1.tgz", + "integrity": "sha512-yvhMiOq+LLmGPgTOYYWjzfapUN4B2/T3nfr8P81bLTiyh0YSx88OAz5y2D/KKS0boF9EoHyhzeMH4ZweMTwjJA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/parser": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/preset-typescript": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", - "@react-router/node": "8.3.0", - "@remix-run/node-fetch-server": "^0.13.3", + "@react-router/node": "8.3.1", + "@remix-run/node-fetch-server": "^0.14.1", "babel-dead-code-elimination": "^1.0.12", "chokidar": "^5.0.0", "dedent": "^1.7.2", @@ -6991,7 +7207,6 @@ "pathe": "^2.0.3", "picocolors": "^1.1.1", "pkg-types": "^2.3.1", - "prettier": "^3.8.3", "react-refresh": "^0.18.0", "semver": "^7.8.1", "tinyglobby": "^0.2.16", @@ -7004,9 +7219,9 @@ "node": ">=22.22.0" }, "peerDependencies": { - "@react-router/serve": "^8.3.0", + "@react-router/serve": "^8.3.1", "@vitejs/plugin-rsc": "~0.5.26", - "react-router": "^8.3.0", + "react-router": "^8.3.1", "react-server-dom-webpack": "^19.2.7", "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0", "vite": "^7.0.0 || ^8.0.0", @@ -7084,18 +7299,18 @@ } }, "node_modules/@react-router/node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@react-router/node/-/node-8.3.0.tgz", - "integrity": "sha512-qw5ibcolE1OcwngiEw6t7LR11Hi6yWEDvj6cfvaYROX+w18JPxc0YSmsdn3Wlc9TOX+Qo8FVcxbXRdc9vojn2w==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@react-router/node/-/node-8.3.1.tgz", + "integrity": "sha512-JQI2vPd0twfHXxRxD1QWxKkCfu5I7bxojgEZFL97EvbJLm1B6p4qeqvGc9hl9g/3ZYbieczQY6ElM3zZjiG4sA==", "license": "MIT", "dependencies": { - "@remix-run/node-fetch-server": "^0.13.3" + "@remix-run/node-fetch-server": "^0.14.1" }, "engines": { "node": ">=22.22.0" }, "peerDependencies": { - "react-router": "8.3.0", + "react-router": "8.3.1", "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { @@ -7193,9 +7408,9 @@ } }, "node_modules/@react-three/postprocessing": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.5.tgz", - "integrity": "sha512-rqxoH8cjq51w5yZ7Re3WNPVVDdSsuVwEYA+Nl90/nFLv0MIxYGjMkqhc6akp5+VLNwZD2aqOd8KcwuZW7NeUKQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.1.1.tgz", + "integrity": "sha512-WG0dhAt/JDGU3eimwq21wuajvXS7SYN5u1kBau/ElTkllFB3CSpwILJNpNctZGxo4Dz157shB66CQb9ZSU/zMQ==", "license": "MIT", "dependencies": { "maath": "^0.10.8", @@ -7204,8 +7419,8 @@ "peerDependencies": { "@react-three/fiber": ">=9.7.0", "postprocessing": "^6.36.0", - "react": "^19.2.0", - "three": ">= 0.182.0" + "react": "^19.0.0", + "three": ">= 0.156.0" } }, "node_modules/@reactively/core": { @@ -7226,21 +7441,21 @@ } }, "node_modules/@redis/bloom": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.1.0.tgz", - "integrity": "sha512-Rzascjd9J9bJsM45T/Z9CTg1QY/B63B6YO8QorLVMeXnbBDsKiSCVR/+GQ061hYPk8FpTzWmPY8tAv2sT+JEtQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.2.1.tgz", + "integrity": "sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==", "license": "MIT", "engines": { "node": ">= 20.0.0" }, "peerDependencies": { - "@redis/client": "^6.1.0" + "@redis/client": "^6.2.1" } }, "node_modules/@redis/client": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.1.0.tgz", - "integrity": "sha512-7u1LefkezJF0HESlhO7ZFLEPfyY+NejP3SGv+Z4pGaT3oM5GVVLa0u3f4rDLUrcw+SRo8IlX9Y8JAONeDdg1Ag==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.2.1.tgz", + "integrity": "sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q==", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2" @@ -7262,39 +7477,39 @@ } }, "node_modules/@redis/json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.1.0.tgz", - "integrity": "sha512-/GFjQA6bu5pG9ClCJAI5Xx4bNXe7UTpxBBlIupBNTrn1+nY860apGnYJuaSCDV2BmEbTidpa7O2qa28oxKx+rg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.2.1.tgz", + "integrity": "sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ==", "license": "MIT", "engines": { "node": ">= 20.0.0" }, "peerDependencies": { - "@redis/client": "^6.1.0" + "@redis/client": "^6.2.1" } }, "node_modules/@redis/search": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.1.0.tgz", - "integrity": "sha512-kS5agg+3yZbrdrt8omrew7FLCD8eOm7tarG1CROekPBRe+QGDR9aOpnHIQaYsYi6wPRTH70nQiF06AIjgURefQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.2.1.tgz", + "integrity": "sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ==", "license": "MIT", "engines": { "node": ">= 20.0.0" }, "peerDependencies": { - "@redis/client": "^6.1.0" + "@redis/client": "^6.2.1" } }, "node_modules/@redis/time-series": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.1.0.tgz", - "integrity": "sha512-uIDBtV8MmG/xpJsRqbGSO4iX6ryj37MLMP82lRpFvI7ykAVe5GyqgxigEbU+uZNv9kDPNMKw3dvI/S/J1BNBzA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.2.1.tgz", + "integrity": "sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw==", "license": "MIT", "engines": { "node": ">= 20.0.0" }, "peerDependencies": { - "@redis/client": "^6.1.0" + "@redis/client": "^6.2.1" } }, "node_modules/@reduxjs/toolkit": { @@ -7324,9 +7539,9 @@ } }, "node_modules/@remix-run/node-fetch-server": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.13.3.tgz", - "integrity": "sha512-UfjOXed/DQteaM5VyTfqTeGpHwyL2J5aoRGY6cydip4tt1ehNNeSwuXCC7AEGE0RWBs/7bgKxYkL/B/+UDe4AA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.14.1.tgz", + "integrity": "sha512-pKenF5ysIN5lDCnCyvoUxDhKP0tfnGagvGbZh01xxHG6DJXSIXWVC9NpLSDGjxcxd1CKdCOKuZFleXBF3pChIg==", "license": "MIT" }, "node_modules/@rolldown/binding-android-arm-eabi": { @@ -8347,49 +8562,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -8404,9 +8619,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -8421,9 +8636,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -8438,9 +8653,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -8455,9 +8670,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -8472,9 +8687,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], @@ -8492,9 +8707,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], @@ -8512,9 +8727,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], @@ -8532,9 +8747,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], @@ -8552,9 +8767,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -8581,76 +8796,10 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "dev": true, - "inBundle": 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/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -8665,9 +8814,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -8682,29 +8831,29 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz", - "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "postcss": "^8.5.15", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" @@ -8731,9 +8880,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", + "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", "dev": true, "license": "MIT", "dependencies": { @@ -8745,9 +8894,18 @@ "redent": "^3.0.0" }, "engines": { - "node": ">=14", + "node": ">=22", "npm": ">=6", "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } } }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { @@ -8758,9 +8916,9 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", - "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz", + "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==", "dev": true, "license": "MIT", "dependencies": { @@ -8786,9 +8944,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.4.tgz", - "integrity": "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew==", + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", "dev": true, "license": "MIT", "engines": { @@ -9223,22 +9381,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/node-notifier": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@types/node-notifier/-/node-notifier-8.0.5.tgz", - "integrity": "sha512-LX7+8MtTsv6szumAp6WOy87nqMEdGhhry/Qfprjm1Ma6REjVzeF7SCyvPtp5RaF6IkXCS9V4ra8g5fwvf2ZAYg==", - "dev": true, + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.0.tgz", + "integrity": "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==", "license": "MIT", "dependencies": { - "@types/node": "*" + "undici-types": "~8.9.0" } }, "node_modules/@types/offscreencanvas": { @@ -9281,9 +9429,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "dev": true, "license": "MIT", "dependencies": { @@ -9291,9 +9439,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -9398,9 +9546,9 @@ } }, "node_modules/@types/three": { - "version": "0.185.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.0.tgz", - "integrity": "sha512-O2Uy8Cj4Nonr8dWUUbifMdPe8B0Mq7EdOHb89S4+kjUw/KhbjTZrUuYlrQ1bpUKG+EP9QJnN7qNxbHGlGoLHMA==", + "version": "0.185.4", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.4.tgz", + "integrity": "sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==", "license": "MIT", "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", @@ -10217,16 +10365,16 @@ } }, "node_modules/@use-gpu/wgsl": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@use-gpu/wgsl/-/wgsl-0.19.0.tgz", - "integrity": "sha512-F/NOU6pJ7ezBiaSTmRIq7UKUZIwo54A1J90frOxKukA4PXTTDsWFtSbOEwgaxES+PBZHpUDmuWJX2KQprQgvZg==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@use-gpu/wgsl/-/wgsl-0.20.0.tgz", + "integrity": "sha512-ARFT+BYZjd23m5gSI4TE7sh34Iz2C6fwccETmRr80O/JtDyNpQZc0DHgbLvqW63N7JZRuW0xsElPU/kQKcUpJg==", "dev": true, "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", - "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, "license": "MIT", "dependencies": { @@ -10238,6 +10386,7 @@ "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "peerDependenciesMeta": { @@ -10246,6 +10395,9 @@ }, "babel-plugin-react-compiler": { "optional": true + }, + "oxc-transform-react": { + "optional": true } } }, @@ -10271,45 +10423,46 @@ "license": "MIT" }, "node_modules/@vitest/browser": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.10.tgz", - "integrity": "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-5.0.0.tgz", + "integrity": "sha512-JC9FG5xIRxPHXJPcdCaluIJcEoeM0IwGQ3xneuJk09LXKHRNs40BqWDWymQikbb20yOpYvzpKzmYgPRuSzKmvg==", "dev": true, "license": "MIT", "dependencies": { - "@blazediff/core": "1.9.1", - "@vitest/mocker": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", + "@blazediff/core": "1.10.0", + "@vitest/mocker": "5.0.0", + "@vitest/ui": "5.0.0", + "@vitest/utils": "5.0.0", + "magic-string": "^1.2.3", "pngjs": "^7.0.0", "sirv": "^3.0.2", - "tinyrainbow": "^3.1.0", - "ws": "^8.19.0" + "tinyrainbow": "^3.1.1", + "ws": "^8.21.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.10" + "vitest": "5.0.0" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.10.tgz", - "integrity": "sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-5.0.0.tgz", + "integrity": "sha512-N+gED9y4/8pypaHjz/x0ah3CjoBr+N0hWWT+Gq4VXtMzyB+rUIdHni0ulzpD1j4H77iWy5Jg8PRMlVn6kjxo6g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/browser": "4.1.10", - "@vitest/mocker": "4.1.10", - "tinyrainbow": "^3.1.0" + "@vitest/browser": "5.0.0", + "@vitest/mocker": "5.0.0", + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "playwright": "*", - "vitest": "4.1.10" + "vitest": "5.0.0" }, "peerDependenciesMeta": { "playwright": { @@ -10317,30 +10470,38 @@ } } }, + "node_modules/@vitest/browser/node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.0.tgz", + "integrity": "sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" + "@vitest/browser": "5.0.0", + "vitest": "5.0.0" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -10348,34 +10509,40 @@ } } }, - "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz", + "integrity": "sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.1.tgz", + "integrity": "sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "@vitest/istanbul-lib-coverage": "1.0.1" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=22" } }, "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0.tgz", + "integrity": "sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.0", "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "magic-string": "^1.2.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -10393,69 +10560,70 @@ } } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "node_modules/@vitest/pretty-format": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-5.0.0.tgz", + "integrity": "sha512-PVRNuB3wpReb4SQEs4zTKM4KWFhQ5pw3spE8naoDJNB5T5aWRzGKHwXcLUllr0WeOTXpB6bSr3CJLo5+7XQSSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "node_modules/@vitest/spy": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0.tgz", + "integrity": "sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "node_modules/@vitest/ui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-5.0.0.tgz", + "integrity": "sha512-h2FIFwggCY2GxUd2UdQoYNVQkOIqEQLPhNREcl3FUiRsdzQep7NWwYbSmhGEA9nFLPDq5pXzRMcBZQU8Py83sg==", "dev": true, "license": "MIT", + "dependencies": { + "@vitest/utils": "5.0.0", + "fflate": "^0.8.3", + "flatted": "^3.4.4", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.1" + }, "funding": { "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "5.0.0" } }, "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-5.0.0.tgz", + "integrity": "sha512-dO++xL3vDfvhTAVimfkuQUA3k+JClIF1i1vAkPqpcGAthRmeWnXmHB7YPViPvgCwviX8u7Y5W1u2N//AaQr3fw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", + "@vitest/pretty-format": "5.0.0", "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "tinyrainbow": "^3.1.1" }, "funding": { "url": "https://opencollective.com/vitest" @@ -10469,19 +10637,19 @@ "license": "MIT" }, "node_modules/@webgpu/types": { - "version": "0.1.71", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", - "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", + "version": "0.1.72", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz", + "integrity": "sha512-0cF7RFM2edNoiIS1ODJp0/Gzv4/xSXhwoR0YCza+OWpJWtn4wmo9DvK91aLlH9+uUnwIriP7ZiC3WitmyhuzBw==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xyflow/react": { - "version": "12.11.2", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.2.tgz", - "integrity": "sha512-eLAlDWJfWnQEhJwGMjlWdAXO9eYllKpliUmPQlAmOLxz6mExXuzMVDUKLMquixgkrtmMFFtug3jGKmYYld12cA==", + "version": "12.11.6", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.6.tgz", + "integrity": "sha512-9XsEJNHjatKYndszKTF/bsU7FOP9dJ6V/EQwzy3oMdtqgBuUq7BjKSwkEo+C7s4qHstHQfwwoHA3E8QfpPxZZQ==", "license": "MIT", "dependencies": { - "@xyflow/system": "0.0.79", + "@xyflow/system": "0.0.82", "classcat": "^5.0.3", "zustand": "^4.4.0" }, @@ -10529,9 +10697,9 @@ } }, "node_modules/@xyflow/system": { - "version": "0.0.79", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.79.tgz", - "integrity": "sha512-czLyOh91NF0hIzbNzwi8I6GlqG23BHh2435OddfI6uiaLH3xdrdygO93gqgH1Bv9mhy8XPFQJOBn1FTq4LvEWA==", + "version": "0.0.82", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.82.tgz", + "integrity": "sha512-4DKnL3CGtCGLRSmgDqaajRVgeksMXq/Yw4wPfdMfm7JvdIiWHGzVYOfFUztiATwdBXZqUU6HhehwUdbV9G23PQ==", "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", @@ -10545,6 +10713,19 @@ "d3-zoom": "^3.0.0" } }, + "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/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -10583,9 +10764,9 @@ } }, "node_modules/adm-zip": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz", - "integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.1.tgz", + "integrity": "sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==", "license": "MIT", "engines": { "node": ">=14.0" @@ -10833,84 +11014,41 @@ } }, "node_modules/archiver": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.2.tgz", - "integrity": "sha512-UQ/2nW7NMl1G+1UnrLypQw1VdT9XZg/ECcKPq7l+STzStrSivFIXIp34D8M5zeNGW5NoOupdYCHv6VySCPNNlw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-8.0.0.tgz", + "integrity": "sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==", "dev": true, "license": "MIT", "dependencies": { - "archiver-utils": "^4.0.1", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^8.0.0", - "graceful-fs": "^4.2.0", + "buffer-crc32": "^1.0.0", + "is-stream": "^4.0.0", "lazystream": "^1.0.0", - "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0", + "readdir-glob": "^3.0.0", + "tar-stream": "^3.0.0", + "zip-stream": "^7.0.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=18" } }, - "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "node_modules/archiver/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": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/archiver-utils/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "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": { - "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/archiver-utils/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" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/archiver/node_modules/tar-stream": { @@ -10978,9 +11116,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", - "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", + "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", "dev": true, "license": "MIT", "dependencies": { @@ -11035,9 +11173,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", - "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "version": "10.5.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.5.tgz", + "integrity": "sha512-uiRYvQYe/nNSzBJ7OUnd2/TZVsAdob3blml44teEpee9Cc1f4rGZFewO+JT3Wo8mgFOSzNqes4FHZn/Qz8WOuw==", "funding": [ { "type": "opencollective", @@ -11054,8 +11192,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.4", - "caniuse-lite": "^1.0.30001799", + "browserslist": "^4.28.9", + "caniuse-lite": "^1.0.30001810", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -11109,16 +11247,16 @@ } }, "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.1.tgz", + "integrity": "sha512-ge1xUVZS91ml09YRMgRGgeKJ4YJpcOiuwteAxFBYLugQyp7cRw+hHej6Ho0vPjvLrjq60bb7JPHH9LAMA1/krA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.4.1", + "@jest/transform": "30.5.1", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -11187,9 +11325,9 @@ } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -11200,16 +11338,16 @@ "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "test-exclude": "^7.0.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { @@ -11262,20 +11400,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", + "babel-plugin-jest-hoist": "30.5.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, "node_modules/bail": { @@ -11401,9 +11539,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.42", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", - "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -11586,6 +11724,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/brace-expansion/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/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -11600,9 +11774,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", - "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", "funding": [ { "type": "opencollective", @@ -11619,11 +11793,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001800", - "electron-to-chromium": "^1.5.387", - "node-releases": "^2.0.50", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" }, "bin": { "browserslist": "cli.js" @@ -11680,13 +11854,13 @@ } }, "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=8.0.0" } }, "node_modules/buffer-equal-constant-time": { @@ -11695,13 +11869,6 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -11853,9 +12020,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001803", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", - "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "funding": [ { "type": "opencollective", @@ -11903,12 +12070,12 @@ } }, "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.0.tgz", + "integrity": "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=22" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -12011,9 +12178,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "dev": true, "license": "MIT" }, @@ -12237,9 +12404,9 @@ "license": "MIT" }, "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz", + "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", "license": "MIT" }, "node_modules/comma-separated-tokens": { @@ -12263,38 +12430,49 @@ } }, "node_modules/compress-commons": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.3.tgz", - "integrity": "sha512-/UIcLWvwAQyVibgpQDPtfNM3SvqN7G9elAPAV7GM0L53EbNWwWiCsWtK8Fwed/APEbptPHXs5PuW+y8Bq8lFTA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-7.0.1.tgz", + "integrity": "sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==", "dev": true, "license": "MIT", "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^7.0.1", + "is-stream": "^4.0.0", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">=18" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/compress-commons/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" + "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/concurrently": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", - "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", + "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.9.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" @@ -12310,6 +12488,19 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/concurrently/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/confbox": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", @@ -12451,17 +12642,34 @@ } }, "node_modules/crc32-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.1.tgz", - "integrity": "sha512-lO1dFui+CEUh/ztYIpgpKItKW9Bb4NWakCRJrnqAbFIYD+OZAwb2VfD5T5eXMw2FNcsDHkQcNl/Wh3iVXYwU6g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-7.0.1.tgz", + "integrity": "sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==", "dev": true, "license": "MIT", "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">=18" + } + }, + "node_modules/crc32-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/cross-env": { @@ -12568,9 +12776,9 @@ "license": "MIT" }, "node_modules/csv-parse": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-7.0.1.tgz", - "integrity": "sha512-+2z7Ar0APQ7Uu6fX4cn+pitRmxjZ1WPBcGmZFKmA74FCyi7Et/XZx8cjNQ5CjbZ4HCOxXCOpRBYvYH08Qa003A==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-7.0.2.tgz", + "integrity": "sha512-uKZghv9UmPkMVLYy//KZ9HFAIJsl7wkhoEdIL0+rhuSY9pZQlhaeGEDPIe+/w7eh81MOql8Q/9+inAGWG6ZHYA==", "license": "MIT" }, "node_modules/d3-array": { @@ -12837,9 +13045,9 @@ } }, "node_modules/decode-uri-component": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", - "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.5.0.tgz", + "integrity": "sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==", "dev": true, "license": "MIT", "engines": { @@ -13071,9 +13279,9 @@ } }, "node_modules/dexie": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.4.tgz", - "integrity": "sha512-jIwsYI8Os2hgnqc6O49YwFDKGc5v5QjGx0wPVp543ip1F53VFAKMLthV2pQosQcVTv3eAskTWYspOx195PM0FQ==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.5.tgz", + "integrity": "sha512-wWCHdihT3dmlUSuNhn5mMZDWSpG0suxjAni7YjjiZdzabZcKmy3uNZGZ7AeYYXBoLbpSXX35fikPLkPC44Osiw==", "license": "Apache-2.0" }, "node_modules/direction": { @@ -13108,9 +13316,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", - "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", + "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -13173,16 +13381,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", - "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "version": "1.5.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", "license": "ISC" }, "node_modules/elkjs": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", - "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", - "license": "EPL-2.0" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.12.0.tgz", + "integrity": "sha512-YZcKynxVxYoKIOEpywEPwCFdg+BTbxQRNf3pbwdDCvc8O3kQD8bmIwSxKU1eOTVc4Xo+VG9Te+575mlfvOrhEQ==", + "license": "EPL-2.0 OR GPL-3.0-or-later" }, "node_modules/embla-carousel": { "version": "8.6.0", @@ -13253,9 +13461,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "dev": true, "license": "MIT", "dependencies": { @@ -13318,9 +13526,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -13601,12 +13809,32 @@ "node": ">= 0.6" } }, + "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.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "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/events-universal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", @@ -13701,18 +13929,18 @@ } }, "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.1.tgz", + "integrity": "sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "@jest/expect-utils": "30.5.1", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.1", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -13786,9 +14014,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz", - "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", "dev": true, "license": "MIT", "dependencies": { @@ -13935,9 +14163,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "dev": true, "funding": [ { @@ -14179,12 +14407,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fkill-cli/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/flatbuffers": { "version": "25.9.23", "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", "license": "Apache-2.0" }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -14247,24 +14495,20 @@ } }, "node_modules/framer-motion": { - "version": "12.42.2", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", - "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.2.0.tgz", + "integrity": "sha512-9E33ebgMaO33w1nN/jEdW8z3/GO483fMi4rqbMG9rt83XgW9QLKRe4NcmJ8s+fQ3O34++UHrIQwlIWGIWTITjA==", "license": "MIT", "dependencies": { - "motion-dom": "^12.42.2", - "motion-utils": "^12.39.0", + "motion-dom": "^13.2.0", + "motion-utils": "^13.0.0", "tslib": "^2.4.0" }, "peerDependencies": { - "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, "react": { "optional": true }, @@ -14305,13 +14549,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -14518,22 +14755,18 @@ "license": "MIT" }, "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", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "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" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14712,13 +14945,6 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "dev": true, - "license": "MIT" - }, "node_modules/gsap": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", @@ -15192,9 +15418,9 @@ "license": "MIT" }, "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", "dev": true, "license": "MIT", "engines": { @@ -15489,18 +15715,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -15516,9 +15730,9 @@ "license": "ISC" }, "node_modules/ink": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ink/-/ink-7.1.0.tgz", - "integrity": "sha512-VWE6/yeLtFCJBNLflyI2OSylyXK1Rc24LuXup8Qt+icwkmmycFNdbn8IkSp6Frc0h1iA0NOvvi1ajW44U/w3Qg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-7.1.1.tgz", + "integrity": "sha512-Y43xxa1ZSPvpmfLHcN5o+OdP8Rf8ykkNJEuKYOUNZKT8wXVNLFTtEm1nSDMQkfBH+YANF4Xuu0hhZ4ejqAtN2w==", "license": "MIT", "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", @@ -15592,6 +15806,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ink/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ink/node_modules/cli-boxes": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", @@ -15712,9 +15938,9 @@ "license": "MIT" }, "node_modules/input-otp": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", - "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.5.0.tgz", + "integrity": "sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==", "license": "MIT", "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", @@ -15860,22 +16086,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", @@ -16114,19 +16324,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -16135,9 +16332,9 @@ "license": "MIT" }, "node_modules/isbot": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.2.1.tgz", - "integrity": "sha512-dJ+LpKyClQZ7NG+j3OensC/mAZkGpukE9YUrgPYvAZj2doVL0edfDgywTUh5CXa0o+nW9a1V9e5+CJTX8+SxRw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.2.2.tgz", + "integrity": "sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==", "license": "Unlicense", "engines": { "node": ">=18" @@ -16284,16 +16481,16 @@ } }, "node_modules/jest": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", - "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.1.tgz", + "integrity": "sha512-3qrR8+ZXFnn7y0H2yjWQNkGGBLBY4zTRoTMAq9zJcgwLLtlyonfsCLviIXK9xuE2KgIyM+M36AFcoK2DgFR36w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/types": "30.4.1", + "@jest/core": "30.5.1", + "@jest/types": "30.5.1", "import-local": "^3.2.0", - "jest-cli": "30.4.2" + "jest-cli": "30.5.1" }, "bin": { "jest": "bin/jest.js" @@ -16322,14 +16519,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "p-limit": "^3.1.0" }, "engines": { @@ -16427,29 +16624,29 @@ } }, "node_modules/jest-circus": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", - "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.1.tgz", + "integrity": "sha512-NgliezXQ6yznqR4W5Gqw++0cZSbBZlF0NknNIAE5VmSJKWOtmWJmWnBq3TSkUOVBTPrT7FGGhVdA8DLWnxo2Sw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.1", + "@jest/expect": "30.5.1", + "@jest/test-result": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-each": "30.5.1", + "jest-matcher-utils": "30.5.1", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.1", + "jest-snapshot": "30.5.1", + "jest-util": "30.5.1", "p-limit": "^3.1.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -16458,6 +16655,19 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-circus/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16492,16 +16702,16 @@ } }, "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -16544,21 +16754,21 @@ } }, "node_modules/jest-cli": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", - "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.1.tgz", + "integrity": "sha512-uwNYepWgaNBCplm42fCIUZTf/tIEHIy5AYvx7eL1BrwIgyjPt+2poouR23UO2yopIP+kV8oZFHfv+l4pg/8prQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/core": "30.5.1", + "@jest/test-result": "30.5.1", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-config": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "yargs": "^17.7.2" }, "bin": { @@ -16720,33 +16930,33 @@ } }, "node_modules/jest-config": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", - "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.1.tgz", + "integrity": "sha512-L8PKM2X/ngG8PxfLMqglKGZjylPgw84bVPUlMY9W/o76TnLqPWrmQgsaT0HrheGdRtpGE5FbmREA0Kvb+Qx5gQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.1", + "@jest/types": "30.5.1", + "babel-jest": "30.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-circus": "30.4.2", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-circus": "30.5.1", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "parse-json": "^5.2.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -16770,6 +16980,19 @@ } } }, + "node_modules/jest-config/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16804,16 +17027,16 @@ } }, "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -16856,16 +17079,29 @@ } }, "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.1.tgz", + "integrity": "sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -16905,16 +17141,16 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -16947,9 +17183,9 @@ } }, "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { @@ -16960,17 +17196,30 @@ } }, "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.1.tgz", + "integrity": "sha512-S1af0TU4v1EZ/AUlkFs/sxf/5KGsbAT9kRgdyoMX/x72y7C8ZEgETE5o1TPnbKRnrbprc5FR/moYLfdRmqEjsQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" + "jest-util": "30.5.1", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17010,16 +17259,16 @@ } }, "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17052,14 +17301,15 @@ } }, "node_modules/jest-environment-jsdom": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", - "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.5.1.tgz", + "integrity": "sha512-8lzKbC/SRbQE24wr1OOJV+aYtDAuVNKBryN6YcFiCcaZZ3I7grcZY7w91BwNvGET0ubKDmomEHZFHMaF+6pAlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/environment-jsdom-abstract": "30.4.1", + "@jest/environment": "30.5.1", + "@jest/environment-jsdom-abstract": "30.5.1", + "@types/jsdom": "^21.1.7", "jsdom": "^26.1.0" }, "engines": { @@ -17075,58 +17325,69 @@ } }, "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.1.tgz", + "integrity": "sha512-LrPj3sPMjsQoOB3jrb8p/sa+XkSFNKo60TTsyc+EB2kxQJHgbwElpXnx1yX25fdFn5958FIObRtcLSHyV8VIAw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.1", + "@jest/fake-timers": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" + "jest-mock": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17146,32 +17407,45 @@ } }, "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.1.tgz", + "integrity": "sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" + "jest-diff": "30.5.1", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17211,16 +17485,16 @@ } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17253,20 +17527,20 @@ } }, "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "picomatch": "^4.0.3", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -17274,6 +17548,19 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17308,16 +17595,16 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17360,42 +17647,25 @@ } }, "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.1.tgz", + "integrity": "sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-util": "30.4.1" + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", "engines": { @@ -17403,34 +17673,33 @@ } }, "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "unrs-resolver": "^1.12.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", - "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.1.tgz", + "integrity": "sha512-JKpXGONDcTaunrNVn8KCl6qAnwl06jIkvv70lhq0Ze47lsPx9sH5HoeEUiXX6iFGnliHN/qpIbQ0l38wrVmXaw==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17493,34 +17762,34 @@ } }, "node_modules/jest-runner": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", - "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.1.tgz", + "integrity": "sha512-FPlQE4+mwnFxXmpPrSi836KV2ZzvK1g6/nPCT8o5BcoDUUNJQeHo1/Qdkoe/QeoL4M7OeJpbnGUhYKhC1VMdaQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.1", + "@jest/environment": "30.5.1", + "@jest/source-map": "30.5.0", + "@jest/test-result": "30.5.1", + "@jest/transform": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.2", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.1", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.1", + "jest-util": "30.5.1", + "jest-watcher": "30.5.1", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17573,32 +17842,33 @@ } }, "node_modules/jest-runtime": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", - "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.1.tgz", + "integrity": "sha512-UB88+NRkK2Tw/OqV7dofYcyiUGrVZtD41k/N0xQOs9fG//57XHK7JLWG52HD1UYpUAhjK4xm6DBvFX3yWudsMA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.1", + "@jest/fake-timers": "30.5.1", + "@jest/globals": "30.5.1", + "@jest/source-map": "30.5.0", + "@jest/test-result": "30.5.1", + "@jest/transform": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", + "cjs-module-lexer": "^2.2.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -17663,9 +17933,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.1.tgz", + "integrity": "sha512-cNWFdSb5xuDGl8hKkAZJ3YtI/PzHpAPFV+HUXWIOG8rMhpDTLVbvAc2d2wRicoYw2wGsJGLaurJT6BLC97bLXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17674,20 +17944,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.1", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.1", + "@jest/types": "30.5.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.4.1", + "expect": "30.5.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", + "jest-diff": "30.5.1", + "jest-matcher-utils": "30.5.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -17695,6 +17965,19 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-snapshot/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17729,16 +18012,16 @@ } }, "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17791,13 +18074,13 @@ "license": "MIT" }, "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -17851,22 +18134,35 @@ "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "node_modules/jest-validate/node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.4.1" + "@sinclair/typebox": "^0.34.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17919,16 +18215,16 @@ } }, "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -17961,19 +18257,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.1.tgz", + "integrity": "sha512-+FHJ7C+S7b3ySfhA1aFmoa6TztsnwZVv84ycPRn0tVN93np2EU4b8C/KadqA3l6igdjgLBTnUotab9ER0HLG8A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/test-result": "30.5.1", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "string-length": "^4.0.2" }, "engines": { @@ -18056,15 +18352,15 @@ } }, "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -18115,9 +18411,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { @@ -18263,28 +18559,28 @@ } }, "node_modules/katex": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.17.0.tgz", - "integrity": "sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==", + "version": "0.18.7", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.7.tgz", + "integrity": "sha512-h+UCwkZ+4Jz8WQ7MLGfj7UVFrRCizGb912fwF4luGdYsC5paYG1vx+jy+KRcC/XkpjGva/P7nAWuxNnPzRvzHw==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], "license": "MIT", "dependencies": { - "commander": "^8.3.0" + "commander": "^15.0.0" }, "bin": { "katex": "cli.js" } }, "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=22.12.0" } }, "node_modules/keygrip": { @@ -18600,9 +18896,9 @@ } }, "node_modules/libheif-js": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/libheif-js/-/libheif-js-1.19.8.tgz", - "integrity": "sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/libheif-js/-/libheif-js-1.23.2.tgz", + "integrity": "sha512-qvHIXtggEsw1lCNCWBYKloL2Z36DJBm0R9ThGiH2JnhKYdeZFLPFkP30Lw4yMskxxhx0bKg1gLrBHX1D2w2pSw==", "license": "LGPL-3.0", "engines": { "node": ">=8.0.0" @@ -18993,9 +19289,9 @@ } }, "node_modules/lucide-react": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz", - "integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.43.0.tgz", + "integrity": "sha512-ubtnda1fVK5ky0PNEpOmB0wiwhpZUyJiol4K14KCm+QKvuCkfUu54Et/rIjyupJpwiJ5Hg+BLQE86/GEsS+IgQ==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -19032,14 +19328,14 @@ } }, "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, @@ -19079,16 +19375,6 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -19113,9 +19399,9 @@ } }, "node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "version": "18.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.12.tgz", + "integrity": "sha512-LEm4ga2YeI2T3GVHj9b0BaDPPk93LLTHMFeMyQbNIzPxc8vCI0y/scy0ZA6z6lXKyT9j9Nhl/OC6ZYKYGuFScA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -20349,31 +20635,21 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "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/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -20451,15 +20727,57 @@ } }, "node_modules/motion-dom": { - "version": "12.42.2", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", - "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.2.0.tgz", + "integrity": "sha512-N6gdSoWRDk0Rh/fVtlqUtLs+fEN3ELFZI3cn3IQE9Mnf3E+Mh8wjO6MstzCOPFh4Yf0L1as5m2eUyYWj8ylVSQ==", "license": "MIT", "dependencies": { - "motion-utils": "^12.39.0" + "motion-utils": "^13.0.0" } }, "node_modules/motion-utils": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.0.0.tgz", + "integrity": "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==", + "license": "MIT" + }, + "node_modules/motion/node_modules/framer-motion": { + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz", + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.43.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion/node_modules/motion-dom": { + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz", + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion/node_modules/motion-utils": { "version": "12.39.0", "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", @@ -20925,6 +21243,13 @@ "node": ">=10" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -20970,38 +21295,10 @@ "dev": true, "license": "MIT" }, - "node_modules/node-notifier": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", - "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.5", - "shellwords": "^0.1.1", - "uuid": "^8.3.2", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", "license": "MIT", "engines": { "node": ">=18" @@ -21112,9 +21409,9 @@ } }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -21239,13 +21536,13 @@ "license": "MIT" }, "node_modules/oxfmt": { - "version": "0.58.0", - "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.58.0.tgz", - "integrity": "sha512-8feG/7NVEHDVwc1OUpP6Pks+TnaDFUw2jLLFIMi5bcmmwxAX2wBQvjSzj62RRTYBf2Op1Wt8xbkmagmPTR5ETg==", + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.67.0.tgz", + "integrity": "sha512-vV7sSiPsaO0mSxdoUdayipVDFPzW/UQ+hrezEHa20+Tx1dnMdZLSRHMT0PdS67FFbhd74M1n08asW21aLGeCrA==", "dev": true, "license": "MIT", "dependencies": { - "tinypool": "2.1.0" + "tinypool": "2.1.2" }, "bin": { "oxfmt": "bin/oxfmt" @@ -21254,28 +21551,28 @@ "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxfmt/binding-android-arm-eabi": "0.58.0", - "@oxfmt/binding-android-arm64": "0.58.0", - "@oxfmt/binding-darwin-arm64": "0.58.0", - "@oxfmt/binding-darwin-x64": "0.58.0", - "@oxfmt/binding-freebsd-x64": "0.58.0", - "@oxfmt/binding-linux-arm-gnueabihf": "0.58.0", - "@oxfmt/binding-linux-arm-musleabihf": "0.58.0", - "@oxfmt/binding-linux-arm64-gnu": "0.58.0", - "@oxfmt/binding-linux-arm64-musl": "0.58.0", - "@oxfmt/binding-linux-ppc64-gnu": "0.58.0", - "@oxfmt/binding-linux-riscv64-gnu": "0.58.0", - "@oxfmt/binding-linux-riscv64-musl": "0.58.0", - "@oxfmt/binding-linux-s390x-gnu": "0.58.0", - "@oxfmt/binding-linux-x64-gnu": "0.58.0", - "@oxfmt/binding-linux-x64-musl": "0.58.0", - "@oxfmt/binding-openharmony-arm64": "0.58.0", - "@oxfmt/binding-win32-arm64-msvc": "0.58.0", - "@oxfmt/binding-win32-ia32-msvc": "0.58.0", - "@oxfmt/binding-win32-x64-msvc": "0.58.0" + "@oxfmt/binding-android-arm-eabi": "0.67.0", + "@oxfmt/binding-android-arm64": "0.67.0", + "@oxfmt/binding-darwin-arm64": "0.67.0", + "@oxfmt/binding-darwin-x64": "0.67.0", + "@oxfmt/binding-freebsd-x64": "0.67.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.67.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.67.0", + "@oxfmt/binding-linux-arm64-gnu": "0.67.0", + "@oxfmt/binding-linux-arm64-musl": "0.67.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.67.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.67.0", + "@oxfmt/binding-linux-riscv64-musl": "0.67.0", + "@oxfmt/binding-linux-s390x-gnu": "0.67.0", + "@oxfmt/binding-linux-x64-gnu": "0.67.0", + "@oxfmt/binding-linux-x64-musl": "0.67.0", + "@oxfmt/binding-openharmony-arm64": "0.67.0", + "@oxfmt/binding-win32-arm64-msvc": "0.67.0", + "@oxfmt/binding-win32-ia32-msvc": "0.67.0", + "@oxfmt/binding-win32-x64-msvc": "0.67.0" }, "peerDependencies": { "svelte": "^5.0.0", @@ -21291,9 +21588,9 @@ } }, "node_modules/oxlint": { - "version": "1.73.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.73.0.tgz", - "integrity": "sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.82.0.tgz", + "integrity": "sha512-+iFM1BGw1ntYJt3QngbJmjbrGxPaKMUADOXOijpWGnYcBPq8YZnQftSS1C+pVcDYy9YxqDVJKQqQkTazTQMboQ==", "dev": true, "license": "MIT", "bin": { @@ -21303,31 +21600,31 @@ "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.73.0", - "@oxlint/binding-android-arm64": "1.73.0", - "@oxlint/binding-darwin-arm64": "1.73.0", - "@oxlint/binding-darwin-x64": "1.73.0", - "@oxlint/binding-freebsd-x64": "1.73.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.73.0", - "@oxlint/binding-linux-arm-musleabihf": "1.73.0", - "@oxlint/binding-linux-arm64-gnu": "1.73.0", - "@oxlint/binding-linux-arm64-musl": "1.73.0", - "@oxlint/binding-linux-ppc64-gnu": "1.73.0", - "@oxlint/binding-linux-riscv64-gnu": "1.73.0", - "@oxlint/binding-linux-riscv64-musl": "1.73.0", - "@oxlint/binding-linux-s390x-gnu": "1.73.0", - "@oxlint/binding-linux-x64-gnu": "1.73.0", - "@oxlint/binding-linux-x64-musl": "1.73.0", - "@oxlint/binding-openharmony-arm64": "1.73.0", - "@oxlint/binding-win32-arm64-msvc": "1.73.0", - "@oxlint/binding-win32-ia32-msvc": "1.73.0", - "@oxlint/binding-win32-x64-msvc": "1.73.0" + "@oxlint/binding-android-arm-eabi": "1.82.0", + "@oxlint/binding-android-arm64": "1.82.0", + "@oxlint/binding-darwin-arm64": "1.82.0", + "@oxlint/binding-darwin-x64": "1.82.0", + "@oxlint/binding-freebsd-x64": "1.82.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.82.0", + "@oxlint/binding-linux-arm-musleabihf": "1.82.0", + "@oxlint/binding-linux-arm64-gnu": "1.82.0", + "@oxlint/binding-linux-arm64-musl": "1.82.0", + "@oxlint/binding-linux-ppc64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-musl": "1.82.0", + "@oxlint/binding-linux-s390x-gnu": "1.82.0", + "@oxlint/binding-linux-x64-gnu": "1.82.0", + "@oxlint/binding-linux-x64-musl": "1.82.0", + "@oxlint/binding-openharmony-arm64": "1.82.0", + "@oxlint/binding-win32-arm64-msvc": "1.82.0", + "@oxlint/binding-win32-ia32-msvc": "1.82.0", + "@oxlint/binding-win32-x64-msvc": "1.82.0" }, "peerDependencies": { - "oxlint-tsgolint": ">=0.24.0", + "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "peerDependenciesMeta": { @@ -21536,16 +21833,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -21562,28 +21849,31 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/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==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { "version": "6.3.0", @@ -21615,9 +21905,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -21708,50 +21998,32 @@ "license": "MIT" }, "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.1" + "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=20" } }, "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=20" } }, "node_modules/pngjs": { @@ -21842,22 +22114,6 @@ "node": ">=10" } }, - "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -21923,6 +22179,16 @@ "react": ">=16.0.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-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-exists/-/process-exists-5.0.0.tgz", @@ -22085,9 +22351,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -22102,13 +22368,13 @@ } }, "node_modules/query-string": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.4.1.tgz", - "integrity": "sha512-lSyJeN3RuaG7DZGWThtYRhk96+kEyZ/+doZpERuWbjeFL+Ok3vEat/swU498rAI0NcVt5/RJp8UDuLz7FckxrA==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.5.1.tgz", + "integrity": "sha512-/zO3RwuRCMTIcEgq6YMv4OrtEE1XzBG7w5N6zc6ydYnkWYOWsnLI/5894hYEzfESfMOT4cHCsRTIdxsSl1KjGg==", "dev": true, "license": "MIT", "dependencies": { - "decode-uri-component": "^0.4.1", + "decode-uri-component": "^0.5.0", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" }, @@ -22329,9 +22595,9 @@ } }, "node_modules/react-is": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", - "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", "license": "MIT" }, "node_modules/react-is-18": { @@ -22525,9 +22791,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.1.tgz", - "integrity": "sha512-ElE/UpOvMLRWtAqbCgyizHXcbws8RPMyN3cBqmdY17Nxr5f01+DEwzOLqhgcy68GSnjtIUFgKWKl8aIgx5aypQ==", + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.4.tgz", + "integrity": "sha512-SOsSj4e9U7vxL15MbgTfl35IO4vxSW4Xb0b1gtJVbRPWv7FOezcgOA00Mi0jP8XqC3y7jOQxpsGs/9zLKHPitA==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -22544,9 +22810,9 @@ } }, "node_modules/react-router": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.0.tgz", - "integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.1.tgz", + "integrity": "sha512-TEOpiO2g0TJHEOJeRVv4amUFun9v1npCKszvcquNvzETUtJ8udV86ah5eFoHT7g26bsBvT6EiIhqulR8eDF++A==", "license": "MIT", "dependencies": { "cookie-es": "^3.1.1" @@ -22633,36 +22899,19 @@ } }, "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-3.0.0.tgz", + "integrity": "sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/readdir-glob/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" + "minimatch": "^10.2.2" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/yqnn" } }, "node_modules/readdirp": { @@ -22680,9 +22929,9 @@ } }, "node_modules/recharts": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.9.2.tgz", - "integrity": "sha512-G4fy+Pk46RaXgwWMh+Nzhyo/lbFAVqXo9gtetlyehe6Ehge9CsgDuOTwQDD+i1+llaLktNBiNq4bhnGlDRXFtw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", "license": "MIT", "workspaces": [ "www" @@ -22805,16 +23054,16 @@ } }, "node_modules/redis": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-6.1.0.tgz", - "integrity": "sha512-0kvUPM8RHP/ZMa0xYaDTcG5e8tIGW6kz6MToVT0V8iOnk6bkXp2jncGRGe2bZEk41lZwiDspUqjZCSk5ohjcKw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-6.2.1.tgz", + "integrity": "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg==", "license": "MIT", "dependencies": { - "@redis/bloom": "6.1.0", - "@redis/client": "6.1.0", - "@redis/json": "6.1.0", - "@redis/search": "6.1.0", - "@redis/time-series": "6.1.0" + "@redis/bloom": "6.2.1", + "@redis/client": "6.2.1", + "@redis/json": "6.2.1", + "@redis/search": "6.2.1", + "@redis/time-series": "6.2.1" }, "engines": { "node": ">= 20.0.0" @@ -23556,9 +23805,9 @@ "license": "MIT" }, "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", "license": "Apache-2.0", "dependencies": { "@img/colour": "^1.1.0", @@ -23572,31 +23821,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.3", - "@img/sharp-darwin-x64": "0.35.3", - "@img/sharp-freebsd-wasm32": "0.35.3", - "@img/sharp-libvips-darwin-arm64": "1.3.2", - "@img/sharp-libvips-darwin-x64": "1.3.2", - "@img/sharp-libvips-linux-arm": "1.3.2", - "@img/sharp-libvips-linux-arm64": "1.3.2", - "@img/sharp-libvips-linux-ppc64": "1.3.2", - "@img/sharp-libvips-linux-riscv64": "1.3.2", - "@img/sharp-libvips-linux-s390x": "1.3.2", - "@img/sharp-libvips-linux-x64": "1.3.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", - "@img/sharp-libvips-linuxmusl-x64": "1.3.2", - "@img/sharp-linux-arm": "0.35.3", - "@img/sharp-linux-arm64": "0.35.3", - "@img/sharp-linux-ppc64": "0.35.3", - "@img/sharp-linux-riscv64": "0.35.3", - "@img/sharp-linux-s390x": "0.35.3", - "@img/sharp-linux-x64": "0.35.3", - "@img/sharp-linuxmusl-arm64": "0.35.3", - "@img/sharp-linuxmusl-x64": "0.35.3", - "@img/sharp-webcontainers-wasm32": "0.35.3", - "@img/sharp-win32-arm64": "0.35.3", - "@img/sharp-win32-ia32": "0.35.3", - "@img/sharp-win32-x64": "0.35.3" + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" }, "peerDependenciesMeta": { "@types/node": { @@ -23650,13 +23899,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "license": "MIT" - }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -23846,13 +24088,19 @@ } }, "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", "license": "MIT", "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/source-map": { @@ -23874,27 +24122,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -24273,9 +24500,9 @@ } }, "node_modules/styled-components": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.3.tgz", - "integrity": "sha512-wYXrhu+JmDjZ1Tv7O0OopGTfztbzun43Pjjhh2H+xc0h5A09dwpZ5FJbrifJDcL8g5TA9btpWOX2+iSRuJTExw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.5.3.tgz", + "integrity": "sha512-vAX79sfpmUerP9fsTTxoTrBDE0RuO4ahjInyWYoohNgqrdg63Ms4q6FJ/o2Fyity82NU3cujOT8Ewl9TThBdwg==", "license": "MIT", "dependencies": { "@emotion/is-prop-valid": "1.4.0", @@ -24400,9 +24627,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "dev": true, "license": "MIT" }, @@ -24627,64 +24854,90 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/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/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "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", + "node_modules/test-exclude/node_modules/glob/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": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/test-exclude/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/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-decoder": { @@ -24698,9 +24951,9 @@ } }, "node_modules/thimbleberry": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/thimbleberry/-/thimbleberry-0.2.10.tgz", - "integrity": "sha512-sWQIKq9FdqYZbsUfw7l7/HqxNmvze0e6/FUlgjBkwYtfd5A30cjfY2M+mHUGlO0+bGZro5J0c7lfeHJ1Zu7/JQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/thimbleberry/-/thimbleberry-0.2.11.tgz", + "integrity": "sha512-ykn/LrFhm6Y33Bo+PKWYS4wAY78WdNNpwOOqYSj0h5ESSYL6Yuz+MR7bs55JOfLvkRf6AlmUWvCzN7jdqXj7Jw==", "dev": true, "license": "ISC", "dependencies": { @@ -24744,9 +24997,9 @@ } }, "node_modules/three-stdlib/node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.11.tgz", + "integrity": "sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==", "license": "MIT" }, "node_modules/tiny-invariant": { @@ -24756,16 +25009,19 @@ "license": "MIT" }, "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -24790,9 +25046,9 @@ } }, "node_modules/tinypool": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", - "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.2.tgz", + "integrity": "sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==", "dev": true, "license": "MIT", "engines": { @@ -24800,9 +25056,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -24829,13 +25085,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -24860,11 +25109,14 @@ } }, "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-4.2.0.tgz", + "integrity": "sha512-TvAJjbHZlYmI323+srtqHQFyJsoWy6mI09ppkuj9+iRsqsVKG9fvTcOP7FHF2UCb0QSYtjEavffrKzdd0XgClg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20" + } }, "node_modules/totalist": { "version": "3.0.1", @@ -24963,9 +25215,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.11", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", - "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "version": "29.4.12", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", + "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", "dev": true, "license": "MIT", "dependencies": { @@ -24975,7 +25227,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.8.0", + "semver": "^7.8.5", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -25067,9 +25319,9 @@ } }, "node_modules/tsx": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", - "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", "dev": true, "license": "MIT", "dependencies": { @@ -25241,9 +25493,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -25457,9 +25709,9 @@ } }, "node_modules/update-browserslist-db": { - "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==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", "funding": [ { "type": "opencollective", @@ -25554,20 +25806,6 @@ "node": ">= 4" } }, - "node_modules/uuid": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", - "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -26094,38 +26332,31 @@ } }, "node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0.tgz", + "integrity": "sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "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": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.0", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -26133,16 +26364,16 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.0", + "@vitest/browser-preview": "5.0.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@vitest/ui": "5.0.0", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -26184,9 +26415,9 @@ } }, "node_modules/vitest-image-snapshot": { - "version": "0.6.51", - "resolved": "https://registry.npmjs.org/vitest-image-snapshot/-/vitest-image-snapshot-0.6.51.tgz", - "integrity": "sha512-3ZEP5snqCJMWfHPXrbrfigRFEQcQmBsmZGVxYRFoWTk+Bt4q1AoAPvx/dIm9t+P+dzpbc1B1z6jjQ9Bi8aXWVA==", + "version": "0.6.52", + "resolved": "https://registry.npmjs.org/vitest-image-snapshot/-/vitest-image-snapshot-0.6.52.tgz", + "integrity": "sha512-IBfzIUlMFl2F991J52W4S+/Tdgj8UzekGSHALkTSIKeHgwFL6ulmi3i7NHhJBoZs1qrxoxghIvItgDlp9Ho8PA==", "dev": true, "license": "MIT", "dependencies": { @@ -26198,6 +26429,16 @@ "vitest": "^4.1.0" } }, + "node_modules/vitest/node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -26211,16 +26452,6 @@ "node": ">=18" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -26274,36 +26505,36 @@ } }, "node_modules/wesl": { - "version": "0.7.27", - "resolved": "https://registry.npmjs.org/wesl/-/wesl-0.7.27.tgz", - "integrity": "sha512-c4Mobv0gTBRgacWGW9t1c69IWkn+OO37qx8Xu+KTESGp/FAyhog3+8Gruhkrq+PY0YOby0jE/prwUYXyNX0EbA==", + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/wesl/-/wesl-0.7.31.tgz", + "integrity": "sha512-raDIkgW3HkOMGLKm4M7RllKVW/1Zc/L2S0FijknKY7IKEagbc4nMyKIjCiVncgRDGAIbEswo6btcGvzOf7Cyvg==", "dev": true, "license": "MIT" }, "node_modules/wesl-gpu": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/wesl-gpu/-/wesl-gpu-0.1.29.tgz", - "integrity": "sha512-Nkh1Zgqm8nHIR/VT30Qlsz36ExJPl128OaWVHswIr//6o6xP/51IIPywfLZA4V0ybxaAPaYxLRRpuzYEVCAOwg==", + "version": "0.1.33", + "resolved": "https://registry.npmjs.org/wesl-gpu/-/wesl-gpu-0.1.33.tgz", + "integrity": "sha512-Oip/WjnG/FwRgM2BDSrhUY6T7zLxdTVl03WyTEV4iPGDJYGFbR63oqh0tBEgGAhr+6xqJQFV1Xh/AGILy5NmlA==", "dev": true, "dependencies": { - "thimbleberry": "^0.2.10", - "wesl": "0.7.27", - "wesl-reflect": "0.0.6" + "thimbleberry": "^0.2.11", + "wesl": "0.7.31", + "wesl-reflect": "0.0.10" } }, "node_modules/wesl-reflect": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/wesl-reflect/-/wesl-reflect-0.0.6.tgz", - "integrity": "sha512-1fXGcI0/uuzf4DTkDBQFCLnRG04BLhza1I/OpLxwvTtc6zu0TzdYKuDA8pYCHB28Hedi/tQ09E0qB5yrSAnEaA==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/wesl-reflect/-/wesl-reflect-0.0.10.tgz", + "integrity": "sha512-liMPps6lk9XE1vf0wL4Zra2j3zbxzeTnScM5OTQE3fz3wN+g/Vi3A0u4mMUVTr1ucdaxZG0e8Swduu1yv5cQMA==", "dev": true, "dependencies": { - "wesl": "0.7.27" + "wesl": "0.7.31" } }, "node_modules/wesl-tooling": { - "version": "0.6.39", - "resolved": "https://registry.npmjs.org/wesl-tooling/-/wesl-tooling-0.6.39.tgz", - "integrity": "sha512-437rv3YF/8/vv5VKsWRPN5T6aduEK3LovFIUZ7sSrc/qlWm0FPNWOOhoCt+DbqumMzs6zDWMq312d/g3SXfGLw==", + "version": "0.6.43", + "resolved": "https://registry.npmjs.org/wesl-tooling/-/wesl-tooling-0.6.43.tgz", + "integrity": "sha512-xICi28VATyMjIEpcBWdw0KLvUu4R3zKAoGNgDIAHk4bMyQ+bNeU00xjEb4KDxygvKyVO0Zs89u4eSnm0w/A7lw==", "dev": true, "dependencies": { "glob": "^13.0.6", @@ -26311,108 +26542,24 @@ "toml": "^3.0.0" }, "peerDependencies": { - "wesl": "^0.7.27" - } - }, - "node_modules/wesl-tooling/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/wesl-tooling/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/wesl-tooling/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/wesl-tooling/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/wesl-tooling/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/wesl-tooling/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "wesl": "^0.7.31" } }, "node_modules/wgsl-test": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/wgsl-test/-/wgsl-test-0.2.30.tgz", - "integrity": "sha512-sFZ5FGa/Z2qzxuS2IkDASL66U5wvXeAsF4BtRG/Wpryr5cTib98azlPDF5sX8lmL/udP46J+ywohCHtusjYMJA==", + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/wgsl-test/-/wgsl-test-0.2.34.tgz", + "integrity": "sha512-RMj+hHbHN0vo3kmc9BN6L5v5lLsySsZnS6izTSl0ldklqzOUI/NV/8TKTD+/C7GMrD4hm2Yxj61rOn4P9+u2pg==", "dev": true, "dependencies": { "glob": "^13.0.6", "pngjs": "^7.0.0", - "thimbleberry": "^0.2.10", - "vitest-image-snapshot": "0.6.51", + "thimbleberry": "^0.2.11", + "vitest-image-snapshot": "0.6.52", "webgpu": "^0.3.8", - "wesl": "0.7.27", - "wesl-gpu": "0.1.29", - "wesl-reflect": "0.0.6", - "wesl-tooling": "0.6.39", + "wesl": "0.7.31", + "wesl-gpu": "0.1.33", + "wesl-reflect": "0.0.10", + "wesl-tooling": "0.6.43", "yargs": "^18.0.0" }, "bin": { @@ -26427,90 +26574,6 @@ } } }, - "node_modules/wgsl-test/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/wgsl-test/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/wgsl-test/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/wgsl-test/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/wgsl-test/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/wgsl-test/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -26926,24 +26989,41 @@ "license": "MIT" }, "node_modules/zip-stream": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", - "integrity": "sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-7.0.5.tgz", + "integrity": "sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==", "dev": true, "license": "MIT", "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "compress-commons": "^7.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">=18" + } + }, + "node_modules/zip-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/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 1e515d61..08a7d37b 100644 --- a/package.json +++ b/package.json @@ -120,74 +120,74 @@ "lint:structure": "node scripts/lint/check-entry-reachability.mjs" }, "dependencies": { - "@chenglou/pretext": "0.0.8", + "@chenglou/pretext": "0.0.9", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.1", - "@google/genai": "2.10.0", + "@google/genai": "2.21.0", "@huggingface/transformers": "^4.2.0", "@inkjs/ui": "^2.0.0", - "@mui/icons-material": "9.2.0", - "@mui/material": "9.2.0", - "@radix-ui/react-accordion": "1.2.16", - "@radix-ui/react-alert-dialog": "1.1.19", - "@radix-ui/react-aspect-ratio": "1.1.11", - "@radix-ui/react-avatar": "1.2.2", - "@radix-ui/react-checkbox": "1.3.7", - "@radix-ui/react-collapsible": "1.1.16", - "@radix-ui/react-context-menu": "2.3.3", - "@radix-ui/react-dialog": "1.1.19", - "@radix-ui/react-dropdown-menu": "2.1.20", - "@radix-ui/react-hover-card": "1.1.19", - "@radix-ui/react-label": "2.1.11", - "@radix-ui/react-menubar": "1.1.20", - "@radix-ui/react-navigation-menu": "1.2.18", - "@radix-ui/react-popover": "1.1.19", - "@radix-ui/react-progress": "1.1.12", - "@radix-ui/react-radio-group": "1.4.3", - "@radix-ui/react-scroll-area": "1.2.14", - "@radix-ui/react-select": "2.3.3", - "@radix-ui/react-separator": "1.1.11", - "@radix-ui/react-slider": "1.4.3", - "@radix-ui/react-slot": "1.3.0", - "@radix-ui/react-switch": "1.3.3", - "@radix-ui/react-tabs": "1.1.17", - "@radix-ui/react-toggle": "1.1.14", - "@radix-ui/react-toggle-group": "1.1.15", - "@radix-ui/react-tooltip": "1.2.12", - "@react-router/node": "8.3.0", + "@mui/icons-material": "9.4.0", + "@mui/material": "9.4.0", + "@radix-ui/react-accordion": "1.2.20", + "@radix-ui/react-alert-dialog": "1.1.23", + "@radix-ui/react-aspect-ratio": "1.1.15", + "@radix-ui/react-avatar": "1.2.6", + "@radix-ui/react-checkbox": "1.3.11", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-context-menu": "2.3.7", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-dropdown-menu": "2.1.24", + "@radix-ui/react-hover-card": "1.1.23", + "@radix-ui/react-label": "2.1.15", + "@radix-ui/react-menubar": "1.1.24", + "@radix-ui/react-navigation-menu": "1.2.22", + "@radix-ui/react-popover": "1.1.23", + "@radix-ui/react-progress": "1.1.16", + "@radix-ui/react-radio-group": "1.4.7", + "@radix-ui/react-scroll-area": "1.2.18", + "@radix-ui/react-select": "2.3.7", + "@radix-ui/react-separator": "1.1.15", + "@radix-ui/react-slider": "1.4.7", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-switch": "1.3.7", + "@radix-ui/react-tabs": "1.1.21", + "@radix-ui/react-toggle": "1.1.18", + "@radix-ui/react-toggle-group": "1.1.19", + "@radix-ui/react-tooltip": "1.2.16", + "@react-router/node": "8.3.1", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@react-three/postprocessing": "^3.0.4", "@reduxjs/toolkit": "2.12.0", - "@xyflow/react": "12.11.2", + "@xyflow/react": "12.11.6", "animejs": "4.5.0", - "autoprefixer": "10.5.2", + "autoprefixer": "10.5.5", "canvas-confetti": "1.9.4", - "chalk": "^5.3.0", + "chalk": "6.0.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", - "csv-parse": "7.0.1", + "csv-parse": "7.0.2", "date-fns": "4.4.0", - "dexie": "4.4.4", - "dompurify": "3.4.13", + "dexie": "4.4.5", + "dompurify": "3.4.15", "dotenv": "^17.3.1", - "elkjs": "^0.11.1", + "elkjs": "0.12.0", "embla-carousel-react": "8.6.0", - "framer-motion": "12.42.2", + "framer-motion": "13.2.0", "gsap": "^3.14.2", "highlight.js": "11.12.0", "icodec": "^0.6.0", - "ink": "7.1.0", + "ink": "7.1.1", "ink-spinner": "^5.0.0", - "input-otp": "1.4.2", + "input-otp": "1.5.0", "isbot": "^5", - "katex": "0.17.0", + "katex": "0.18.7", "leaflet": "^1.9.4", "leva": "^0.10.1", "libheif-js": "^1.19.8", - "lucide-react": "1.23.0", - "marked": "18.0.5", + "lucide-react": "1.43.0", + "marked": "18.0.12", "marked-highlight": "^2.2.3", "motion": "12.42.2", "n_apt_canvas": "file:./packages/n_apt_canvas", @@ -202,16 +202,16 @@ "react-leaflet": "^5.0.0", "react-markdown": "^10.1.0", "react-redux": "9.3.0", - "react-resizable-panels": "4.12.1", + "react-resizable-panels": "4.12.4", "react-responsive-masonry": "2.7.2", - "react-router": "8.3.0", - "recharts": "3.9.2", - "redis": "6.1.0", + "react-router": "8.3.1", + "recharts": "3.10.1", + "redis": "6.2.1", "rehype-highlight": "^7.0.2", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", - "sonner": "2.0.7", - "styled-components": "6.4.3", + "sonner": "2.0.8", + "styled-components": "6.5.3", "tailwind-merge": "3.6.0", "three": "0.185.1", "unist-util-visit": "^5.1.0", @@ -221,12 +221,12 @@ }, "devDependencies": { "@ladle/react": "^5.1.1", - "@playwright/test": "1.61.1", - "@react-router/dev": "8.3.0", - "@tailwindcss/postcss": "4.3.2", - "@tailwindcss/vite": "4.3.2", + "@playwright/test": "1.63.0", + "@react-router/dev": "8.3.1", + "@tailwindcss/postcss": "4.3.3", + "@tailwindcss/vite": "4.3.3", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/dompurify": "^3.0.5", @@ -234,25 +234,24 @@ "@types/jest": "^30.0.0", "@types/katex": "^0.16.8", "@types/leaflet": "^1.9.21", - "@types/node": "26.1.1", - "@types/node-notifier": "^8.0.5", - "@types/react": "19.2.17", + "@types/node": "26.5.0", + "@types/react": "19.2.18", "@types/react-dom": "^19.2.3", "@types/react-katex": "^3.0.4", "@types/styled-components": "^5.1.36", - "@types/three": "0.185.0", + "@types/three": "0.185.4", "@types/ws": "8.18.1", "@typescript/typescript6": "6.0.2", - "@use-gpu/wgsl": "^0.19.0", - "@vitejs/plugin-react": "6.0.3", - "@vitest/browser-playwright": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@webgpu/types": "0.1.71", + "@use-gpu/wgsl": "0.20.0", + "@vitejs/plugin-react": "6.1.1", + "@vitest/browser-playwright": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@webgpu/types": "0.1.72", "agent-react-devtools": "^0.4.0", "agentation": "^3.0.1", - "archiver": "^6.0.1", + "archiver": "8.0.0", "better-sqlite3": "^12.4.1", - "concurrently": "10.0.3", + "concurrently": "10.0.5", "express": "5.2.1", "express-fileupload": "^1.5.2", "express-rate-limit": "^8.5.2", @@ -263,20 +262,19 @@ "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^30.2.0", "jest-transform-stub": "^2.0.0", - "node-notifier": "^10.0.1", - "oxfmt": "0.58.0", - "oxlint": "1.73.0", - "playwright": "1.61.1", + "oxfmt": "0.67.0", + "oxlint": "1.82.0", + "playwright": "1.63.0", "react-devtools-core": "^7.0.1", "resize-observer-polyfill": "^1.5.1", - "tailwindcss": "4.3.2", - "ts-jest": "29.4.11", - "tsx": "4.23.0", + "tailwindcss": "4.3.3", + "ts-jest": "29.4.12", + "tsx": "4.23.13", "typescript": "7.0.2", "vite": "8.2.2", "vite-plugin-glsl": "^1.5.6", - "vitest": "4.1.10", - "wgsl-test": "0.2.30" + "vitest": "5.0.0", + "wgsl-test": "0.2.34" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -297,8 +295,9 @@ "braces": "^3.0.3", "micromatch": "^4.0.8", "hono": "^4.12.34", - "sharp": "0.35.3", - "adm-zip": "^0.6.0", + "sharp": "0.35.4", + "adm-zip": "^0.6.1", + "toml": "4.2.0", "shell-quote": "^1.9.0", "fast-uri": "^3.1.5", "js-yaml": "^3.15.1", diff --git a/pages/how-do-they-do-it.md b/pages/how-do-they-do-it.md index 6428855f..3a1569fb 100644 --- a/pages/how-do-they-do-it.md +++ b/pages/how-do-they-do-it.md @@ -1328,11 +1328,70 @@ With I/Q in mind and heterodyning, this is the theoretical minimum of data that +### Estimated Calories + +Given that 24/7 read and write and the flurry of effects, it's not exactly a no-cost mechanism to one's brain and nervous system. Of course, forcible all day states of mind and body certainly consume calories. Below, is a ChatGPT generated table that shows a list of several passive brain-body activities and the caloric consumption they incur: + +| Activity | Calories per minute | Calories per hour | Calories per day | +|---|---:|---:|---:| +| 🧠 Brain at rest | **0.21 kcal** | **12.5 kcal** | **300 kcal** | +| 🧠 Thinking / concentration | **0.22 kcal** | **13 kcal** | — | +| 💭 Imagining / daydreaming | **0.22 kcal** | **13 kcal** | — | +| 🗣️ Talking, seated | **1.2 kcal** | **72 kcal** | — | +| 🧍 Standing quietly | **1.3 kcal** | **78 kcal** | — +🗣️ Animated conversation | **1.4 kal** | **84 kcal** | — | +| 😱 Strong emotional arousal | **1.1–1.5 kcal** | **66–90 kcal** | — | +| 📺 Watching TV | **1.0 kcal** | **60** | —| +| 🚶 Walking, 3 km/h | **3.0 kcal** | **180 kcal** | — | +| 🚶 Walking, 5 km/h | **4.5 kcal** | **270 kcal** | — | +| 🏃 Running, 10 km/h | **11.7 kcal** | **700 kcal** | — | +| 😴 Sleeping | **0.9 kcal** | **54 kcal** | **~432 kcal / 8 h*| +| || + +Taking what this table has and what I've experienced: talking every second, dozens of all day psychological, perceptual and physiological effects, constant narrative-based mental suppression and so much more–we can most certainly come up with an approximated number both for the host and participants of the livestream interactive. + + +### Host (me) — 16 hours/day/wake to sleep, excluding dreams +| Activity | Duration | Approx. total kcal | Approx. extra kcal above rest | +|---|---:|---:|---:| +| 🧠 Thinking / concentration | 16 h | ~208 | **~16** | +| 😱 Strong emotional arousal | 16 h | ~1,056–1,440 | **~192–576** | +| 💭 Imagining / daydreaming | 16 h | ~208 | **~16** | +| 👂 Listening | 16 h | ~960 | **~0–16** | +| 🗣️ Animated conversation | 16 h | ~1,344 | **~384** | +| **Activity-related extra** | **16 h** | | **~608–1,008 kcal/day** | + +### Active participant (other hostages; different POV) —16 hours/day/wake to sleep, excluding dreams +| Activity | Duration | Approx. total kcal | Approx. extra kcal above rest | +|---|---:|---:|---:| +| 🧠 Thinking / concentration | 16 h | ~208 | **~16** | +| 😱 Strong emotional arousal | 16 h | ~1,056–1,440 | **~192–576** | +| 💭 Imagining / daydreaming | 16 h | ~208 | **~16** | +| 👂 Listening | 16 h | ~960 | **~0–16** | +| 🗣️ Animated conversation | 16 h | ~1,344 | **~384** | +| 📺 Watching TV | 16 h | ~960 | **~0–16** | +| 📖 Reading | 6–8 h | ~360–480 | **~0–24** | +| **Activity-related extra** | **16–24 h** | | **~608–1,048 kcal/day** | + + + +**Assumptions & disclaimer:** Estimates are based on a 70 kg sedentary adult over an approximately 16-hour active period. Calories are expressed relative to resting/sedentary metabolism to reduce double-counting when activities overlap. Mental activities such as thinking and imagining generally add little energy expenditure above baseline, while emotional arousal varies substantially between individuals. These figures are illustrative estimates rather than precise measurements, and overlapping activities should not be interpreted as independent calorie expenditures. + + + +While not quite sedentary while survival mode, I assume that the physiological and psychological interactive's presence most certainly doesn't come for "free" to the victim, even if the agency continues to orchestrate it's trap as it is a fact of life. For the central victim, my ChatGPT estimates put it in the ballpark of **608-1,048 calories per day** of it being on 24/7 with just basic comparisons of analogous activities. For other hostages with a different experience/non first-person POV present in the same livestream, they are around **608–1,008 calories per day.** + +That's a lot of calories consumed from a signal pretty much overwriting your brain and nervous system all day, if based on the 70kg weight figure, that can range from **a quarter to half the calories of a typical day** simply consumed from the inescapable, passive all day experience of being narrated around, all day streaming use of one's vocal cords, and terrorizing states of mind and body. + + ## Conclusion TODO -## ### How long have I been trapped? +
+
+ +### How long have I been trapped? ```React::DaysSince ``` @@ -1349,6 +1408,9 @@ TODO +
+
+ ![The EFF's Headquarters in San Francisco](/md-preview/images/11_5_2025.jpeg) *I was ignored by the EFF. Instead of legal aid, I myself poured over nearly every OIG report, unclassified release, publicized policy, etc. for hours and hours.* @@ -1473,11 +1535,11 @@ It boils down to these 8 simple terms. The NSA hacked my brain and body with the experience going from manufactured states of mind while inundated by a spatial performance then escalating to a horrifically grotesque and even more shocking livestream interactive that introduced others and an antagonistic, villanous series of narratives and crushing, tormenting states of mind and body, deeply offensive perceptions and horrific violations of conscience. -I spent years (since late September 2018 when it "spontaneously" activated to present; ongoing) trapped and went from techie to oppressed in abject poverty up against the violent and unrelenting. I spent the years enduring narrative capture: senseless violence, sexual assault and a deeply thought out, thousand-hours long script of an invasive neurotechnological reckoning/kidnapping (whose outcome is positioned for political terrorism based upon identity politics and cyber capability's novelty and mysterious nature; designed as a Darwinian leftist horror for the everyday person through extreme pressures, nihilist suffering and extreme pain while the victim is naturally supposed to seek the interactive's completion and freedom). +I spent years (since late September 2018 when it "spontaneously" activated to present; ongoing) trapped and went from techie to oppressed in abject poverty up against the violent and unrelenting. I spent the years enduring narrative capture: senseless violence, sexual assault and a deeply thought out, thousand-hours long script of an invasive neurotechnological reckoning/kidnapping (whose outcome is positioned for political terrorism based upon identity politics and cyber capability's novelty and mysterious nature; designed as a Darwinian-psychopathic horror for the everyday person through extreme pressures, nihilist suffering and extreme pain while the victim is naturally supposed to seek the interactive's completion as a Hail Mary for freedom, sponsored and concealed by Democrats). By accepting that there had to be a way out and working the problem out from its building blocks, I had come to realize how what clearly working and violating me was possible—and I was in a long string of victims in a politicized sink or swim psychological-physiological interactive and prison of mind. -[^scan-state] Term from the NSA's Research Directorate that is a good way to describe the triangulation. + [^apt]: National Aeronautical Space Agency (NASA). Constructing Inexpensive Automatic Picture-Transmission Ground Stations. https://ntrs.nasa.gov/api/citations/19680010155/downloads/19680010155.pdf @@ -1489,6 +1551,8 @@ By accepting that there had to be a way out and working the problem out from its [^vocab]: Ceane Lamerez. April 29 2026. **Technical Vocabulary** *Expanded vocabulary generated from AI systems which includes MetaAI (Meta) and ChatGPT (OpenAI) with minor edits for the appropriate context and interpretation by the author (me).* #vocab +[^scan-stare]: **“Scan-and-stare”** is a term used in an NSA technology assessment for systems that can scan a wide range of radio frequencies while also focusing on specific signals at the same time. I use the term here by analogy to describe combining observations from multiple points to triangulate a signal source. See *Superconducting Technology Assessment*, NSA, Office of Corporate Assessments, p. 157. [PDF](https://www.nitrd.gov/pubs/nsa/sta.pdf) + --- *No classified information was used for this article, only the author's lived experience and prolonged exposure with a potentially misclassified system(s).* @@ -1497,7 +1561,7 @@ By accepting that there had to be a way out and working the problem out from its *It is not the author's intent to disclose information for the purpose of compromising national security, defeating lawful security measures, enabling surveillance or cyber operations, or facilitating harm to the United States or any person. Nothing herein is intended to jeopardize national security or provide operational assistance to a foreign government, hostile actor, or other third party.* -*The signal captures, images, observed features, and experiences documented in this article are real and truthfully presented. They are not simulations, fabrications, or hypothetical examples unless expressly identified as such. The author cannot presently reconstruct the underlying method or technical mechanisms in full. Any mathematical modeling, pseudocode, reconstructed mechanisms, or technical explanations are therefore inferred from the documented observations and are provided for illustrative and analytical purposes only.* +*The signal captures, images, observed features, and experiences documented in this article are **real and truthfully presented**. They are not simulations, fabrications, or hypothetical examples unless expressly identified as such. The author cannot presently reconstruct the underlying method or technical mechanisms in full. Any mathematical modeling, pseudocode, reconstructed mechanisms, or technical explanations are therefore inferred from the documented observations and are provided for illustrative and analytical purposes only.* --- diff --git a/scripts/build/backend-handoff-proxy.ts b/scripts/build/backend-handoff-proxy.ts index b7e27b4c..8a02ecbb 100644 --- a/scripts/build/backend-handoff-proxy.ts +++ b/scripts/build/backend-handoff-proxy.ts @@ -3,6 +3,7 @@ import http from "node:http"; import { createRequire } from "node:module"; import path from "node:path"; import { spawnSync } from "node:child_process"; +import { appRuntimeDirectory, backendHandoffLockPath, backendTargetPath } from "./runtimePaths"; // Force the Node entrypoint. Jest's jsdom environment otherwise resolves the // package "browser" stub, which exports an empty object. @@ -26,10 +27,11 @@ export const getDefaultBackendProxyUrl = (): string => "http://127.0.0.1:8765"; export function readBackendTarget(targetFile: string): BackendTarget { const parsed = JSON.parse(fs.readFileSync(targetFile, "utf8")) as Partial; - if (typeof parsed.host !== "string" || !Number.isInteger(parsed.port) || parsed.port <= 0 || parsed.port > 65535) { + const port = parsed.port; + if (typeof parsed.host !== "string" || typeof port !== "number" || !Number.isInteger(port) || port <= 0 || port > 65535) { throw new Error(`Invalid backend target in ${targetFile}`); } - return { host: parsed.host, port: parsed.port }; + return { host: parsed.host, port }; } export function writeBackendTarget(targetFile: string, target: BackendTarget): void { @@ -191,9 +193,10 @@ if (isMainModule) { for (let index = 2; index < process.argv.length - 1; index += 2) { args.set(process.argv[index], process.argv[index + 1]); } - const targetFile = path.resolve(args.get("--target-file") || ".n-apt-backend-target.json"); + const targetFile = path.resolve(args.get("--target-file") || backendTargetPath); const listenPort = Number(args.get("--port") || 8765); - const lockFile = path.resolve(".n-apt-backend-handoff-proxy.lock"); + fs.mkdirSync(appRuntimeDirectory, { recursive: true }); + const lockFile = backendHandoffLockPath; const releaseLock = acquireProxyLock(lockFile); process.once("exit", releaseLock); const server = createBackendHandoffProxy({ listenPort, targetFile }); diff --git a/scripts/build/build-orchestrator.tsx b/scripts/build/build-orchestrator.tsx index 2e069987..2bb130a6 100644 --- a/scripts/build/build-orchestrator.tsx +++ b/scripts/build/build-orchestrator.tsx @@ -7,10 +7,10 @@ import { spawn, spawnSync } from 'child_process'; import net from 'node:net'; import os from 'node:os'; import chalk from 'chalk'; -import notifier from 'node-notifier'; import path from 'node:path'; import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; +import { notify } from './desktopNotify'; import { getDeviceAwareRuntimeSummaryState, getRuntimeSummaryState, @@ -30,6 +30,14 @@ import { runRustHotReloadValidation, type RustHotReloadPhase, } from './rustHotReloadGate'; +import { + HOT_RELOAD_USB_RELEASE_SETTLE_MS, + extractDeviceState, + formatSdrReadyFailure, + signalProcessTree, + waitForProcessExit, + waitForSdrReady, +} from './rustHotReloadHandoff'; import { acquireBuildOrchestratorLock } from './buildOrchestratorLock'; import { writeBackendTarget } from './backend-handoff-proxy'; import { removeActiveChild } from './processLifecycle'; @@ -47,6 +55,7 @@ import { type RebuildStatusStep, } from './cargoBuildProgress'; import { startDevStatusServer, type DevStatusServerHandle } from './devStatusServer'; +import { appRuntimeDirectory, backendTargetPath, buildOrchestratorLockPath, rebuildStatusPath } from './runtimePaths'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -54,7 +63,7 @@ const isMainModule = import.meta.url === `file://${process.argv[1]}`; const hasInteractiveTty = Boolean(isMainModule && process.stdin.isTTY && process.stdout.isTTY); const backendProxyPort = 8765; const backendInitialPort = 8766; -const backendTargetFile = path.resolve('.n-apt-backend-target.json'); +const backendTargetFile = backendTargetPath; const findAvailableTcpPort = async (startingPort: number): Promise => { for (let port = startingPort; port <= 65535; port += 1) { @@ -409,7 +418,7 @@ const ProcessStep = ({ process, isActive, showOutput, onToggleOutput, hotReloadL {shouldShowOutput && process.buildOutput && process.buildOutput.length > 0 && ( {process.buildOutput.slice(-10).map((line, idx) => ( - {line} + {line} ))} )} @@ -425,6 +434,7 @@ const BuildOrchestrator = () => { const activeChildrenRef = useRef>>([]); const buildStartedRef = useRef(false); const intentionalRustKillRef = useRef(false); + const intentionalRustCandidateKillRef = useRef(false); const rustPidRef = useRef(undefined); const rustCandidatePidRef = useRef(undefined); const backendPortRef = useRef(backendInitialPort); @@ -437,7 +447,7 @@ const BuildOrchestrator = () => { const writeRebuildStatus = useCallback((patch: Partial) => { rebuildStatusRef.current = { ...rebuildStatusRef.current, ...patch }; try { - fs.writeFileSync('.rebuild_status.json', `${JSON.stringify(rebuildStatusRef.current)}\n`); + fs.writeFileSync(rebuildStatusPath, `${JSON.stringify(rebuildStatusRef.current)}\n`); } catch { // Best-effort status for the Vite /rebuild-status endpoint. } @@ -865,6 +875,9 @@ const BuildOrchestrator = () => { const args = typeof command === 'string' ? [] : command.args; const shouldUseShell = typeof command === 'string' && !isRustBackendBinary; return new Promise((resolve) => { + // Declared outside the try so the catch below can settle the promise + // instead of throwing a ReferenceError while handling a spawn failure. + let resolved = false; try { if (pidKey === 'redisPid') { fs.mkdirSync('.redis_data', { recursive: true }); @@ -877,7 +890,6 @@ const BuildOrchestrator = () => { cwd: './', // Run from project root env: typeof command === 'string' ? process.env : { ...process.env, ...command.env }, })); - let resolved = false; let crashReported = false; const reportCrash = (reason: string) => { if (crashReported || shutdownRequestedRef.current) return; @@ -947,6 +959,17 @@ const BuildOrchestrator = () => { return; } + if (pidKey === 'rustCandidatePid') { + if (intentionalRustCandidateKillRef.current) { + intentionalRustCandidateKillRef.current = false; + return; + } + addLog(chalk.red(`${description} exited unexpectedly (${statusText}).`)); + appendErrorDetail(`${description} exited unexpectedly (${statusText})`); + setBuildState(prev => ({ ...prev, rustCandidatePid: undefined })); + return; + } + if (pidKey === 'rustPid') { if (intentionalRustKillRef.current) { intentionalRustKillRef.current = false; @@ -1466,7 +1489,13 @@ exit 1 } else if (step.isBackground && step.pidKey) { success = await startBackgroundProcess(step.command, step.description, step.pidKey); } else if (step.isBackground) { - success = await startBackgroundProcess(step.command, step.description, 'vitePid'); + const backgroundCommand = step.command; + if (backgroundCommand === undefined) { + appendErrorDetail(`${stepLabel}: background steps require a command`); + success = false; + } else { + success = await startBackgroundProcess(backgroundCommand, step.description, 'vitePid'); + } } else { const _stepIndex = step.index; if (typeof step.command !== 'string') { @@ -1611,16 +1640,23 @@ exit 1 message: string; }> => { try { - const response = await fetch('http://localhost:8765/status'); - const data = await response.json(); + const response = await fetch('http://localhost:8765/api/agent/status'); + const payload = await response.json(); + const device = extractDeviceState(payload); + if (!device) { + return { + deviceState: null, + message: 'Backend not responding', + }; + } return { - deviceState: typeof data.device_state === 'string' ? data.device_state : null, + deviceState: device.state || null, message: - data.device_state === 'loading' - ? 'RTL-SDR Connecting' - : data.device_connected - ? 'RTL-SDR Connected' - : 'RTL-SDR Disconnected, Mock APT Running', + device.connected + ? `${device.info || 'SDR'} Connected` + : device.state === 'loading' + ? 'SDR Connecting' + : 'SDR Disconnected, Mock APT Running', }; } catch { return { @@ -1674,7 +1710,7 @@ exit 1 const msg = !hadServicesRef.current ? `✓ Finished building and running at http://localhost:5173` : `✓ ${deviceStatus}`; - notifier.notify({ + notify({ title: 'N-APT 🧠', message: msg, icon: path.join(__dirname, 'public/icon-5112.png'), @@ -1786,6 +1822,7 @@ exit 1 if ( waitStartedAt != null && isRebuildStatusStale({ + rebuilding: false, pending: true, phase: 'waiting', startedAt: waitStartedAt, @@ -1814,8 +1851,7 @@ exit 1 } const oldPid = rustPidRef.current ?? buildState.rustPid; - const oldPort = backendPortRef.current; - const newPort = await findAvailableTcpPort(oldPort + 1); + const newPort = await findAvailableTcpPort(backendPortRef.current + 1); const candidateCommand: BackgroundCommand = { executable: isNativeWindows ? path.resolve('target/dev-fast/n-apt-backend.exe') @@ -1826,6 +1862,31 @@ exit 1 }, }; + // A USB SDR is exclusive: the outgoing backend has to release the + // interface before the replacement probes for it, or every open attempt + // collides (`usb_claim_interface error -3`) and the replacement silently + // falls back to Mock APT while still answering HTTP. Stop the old owner + // first and wait for it to actually exit. + if (oldPid) { + intentionalRustKillRef.current = true; + addLogRef.current(chalk.green('[Watcher] Stopping current Rust backend to release the SDR...')); + signalProcessTree(oldPid, 'SIGTERM'); + + if (!(await waitForProcessExit({ pid: oldPid }))) { + addLogRef.current(chalk.yellow('[Watcher] Old Rust backend did not exit gracefully; forcing shutdown.')); + signalProcessTree(oldPid, 'SIGKILL'); + await waitForProcessExit({ pid: oldPid, timeoutMs: 2000 }); + } + } + + rustPidRef.current = undefined; + setBuildState(prev => ({ ...prev, rustPid: undefined })); + + // Give the OS/USB stack a beat to finish tearing down the old handle. + await new Promise((resolve) => setTimeout(resolve, HOT_RELOAD_USB_RELEASE_SETTLE_MS)); + + if (shutdownRequestedRef.current) return false; + addLogRef.current(chalk.green(`[Watcher] Starting replacement Rust backend on ${newPort}...`)); const candidateStarted = await startBackgroundProcessRef.current( candidateCommand, @@ -1835,6 +1896,12 @@ exit 1 const candidatePid = rustCandidatePidRef.current; if (!candidateStarted || !candidatePid) return false; + const abortCandidate = () => { + intentionalRustCandidateKillRef.current = true; + signalProcessTree(candidatePid, 'SIGTERM'); + rustCandidatePidRef.current = undefined; + }; + const waitForBackendReady = async (timeoutMs = 15000) => { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { @@ -1851,16 +1918,39 @@ exit 1 }; if (!(await waitForBackendReady())) { - addLogRef.current(chalk.yellow('[Watcher] Replacement Rust backend did not become ready; keeping the old backend.')); - try { - process.kill(-candidatePid, 'SIGTERM'); - } catch { - try { process.kill(candidatePid, 'SIGTERM'); } catch {} - } + addLogRef.current(chalk.yellow('[Watcher] Replacement Rust backend did not become ready.')); + abortCandidate(); return false; } - if (shutdownRequestedRef.current) return false; + if (shutdownRequestedRef.current) { + abortCandidate(); + return false; + } + + // HTTP readiness is not enough. The backend answers /status while running + // Mock APT, so a failed handoff would otherwise look like a successful + // reload. Wait until the replacement actually owns a supported radio (or + // until the source inventory proves that none is attached). + const sdrReady = await waitForSdrReady({ + baseUrl: `http://127.0.0.1:${newPort}`, + isCancelled: () => shutdownRequestedRef.current, + }); + if (!sdrReady.ok) { + addLogRef.current(chalk.yellow( + `[Watcher] Replacement Rust backend did not acquire the SDR: ${formatSdrReadyFailure(sdrReady)}.`, + )); + abortCandidate(); + return false; + } + if (sdrReady.outcome === 'connected') { + addLogRef.current(chalk.green('[Watcher] Replacement Rust backend owns the SDR.')); + } + + if (shutdownRequestedRef.current) { + abortCandidate(); + return false; + } writeBackendTarget(backendTargetFile, { host: '127.0.0.1', port: newPort }); backendPortRef.current = newPort; @@ -1875,51 +1965,9 @@ exit 1 addLogRef.current(chalk.green('[Watcher] Replacement Rust backend is ready; switched proxy target.')); // Proxy closes existing websockets on target change; give clients a beat - // to land on the replacement before shutting down the old process. + // to land on the replacement backend. await new Promise((resolve) => setTimeout(resolve, 250)); - if (shutdownRequestedRef.current) return false; - - if (oldPid) { - try { - intentionalRustKillRef.current = true; - process.kill(-oldPid, 'SIGTERM'); - } catch { - try { process.kill(oldPid, 'SIGTERM'); } catch {} - } - - const deadline = Date.now() + 5000; - let exited = false; - while (Date.now() < deadline) { - try { - process.kill(oldPid, 0); - } catch { - exited = true; - break; - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } - if (!exited) { - addLogRef.current(chalk.yellow('[Watcher] Old Rust backend did not exit gracefully; forcing shutdown.')); - try { process.kill(-oldPid, 'SIGKILL'); } catch {} - try { process.kill(oldPid, 'SIGKILL'); } catch {} - } - } - - try { - const response = await fetch(`http://127.0.0.1:${newPort}/status`, { - method: 'GET', - cache: 'no-store', - }); - if (!response.ok) { - addLogRef.current(chalk.yellow('[Watcher] Replacement backend lost readiness after cutover.')); - return false; - } - } catch { - addLogRef.current(chalk.yellow('[Watcher] Replacement backend unreachable after cutover.')); - return false; - } - return true; }; @@ -2098,7 +2146,7 @@ exit 1 } if (!buildTimedOut && validationResult.stage === 'restarted') { - notifier.notify({ + notify({ title: 'N-APT', message: '✓ Rust backend reloaded successfully', icon: path.join(__dirname, 'public/icon-5112.png'), @@ -2114,6 +2162,7 @@ exit 1 try { watcher = fs.watch(srcRsPath, { recursive: true }, (_eventType, filename) => { + if (filename === null) return; if (!isRustSourceChange(srcRsPath, filename)) return; hotReloadGate.recordChange(filename.toString()); scheduleRebuild(); @@ -2428,7 +2477,7 @@ async function runNonTtyBuild() { }; // Notify build started - notifier.notify({ + notify({ title: 'N-APT', message: 'Staring build...', icon: path.join(__dirname, 'public/icon-5112.png'), @@ -2538,7 +2587,7 @@ async function runNonTtyBuild() { index: 7, description: 'Building and starting Rust backend', run: async () => { - notifier.notify({ + notify({ title: 'N-APT', message: 'Almost done building...', icon: path.join(__dirname, 'public/icon-5112.png'), @@ -2595,7 +2644,7 @@ async function runNonTtyBuild() { let nonTtyStartedAt = 0; const writeNonTtyStatus = (patch: Partial) => { try { - fs.writeFileSync('.rebuild_status.json', `${JSON.stringify({ + fs.writeFileSync(rebuildStatusPath, `${JSON.stringify({ rebuilding: true, buildStartedAt: nonTtyStartedAt || Date.now(), errorCount: errorDetails.length, @@ -2643,7 +2692,7 @@ async function runNonTtyBuild() { ? `Failed to build, error with ${failedComponents[0]}` : `Failed to build, errors with ${failedComponents.slice(0, -1).join(', ')} and ${failedComponents[failedComponents.length - 1]}`; - notifier.notify({ + notify({ title: 'N-APT', message: errorMsg, icon: path.join(__dirname, 'public/icon-5112.png'), @@ -2652,7 +2701,7 @@ async function runNonTtyBuild() { } } - notifier.notify({ + notify({ title: 'N-APT 🧠', message: '✓ Finished building and running at http://localhost:5173', icon: path.join(__dirname, 'public/icon-5112.png'), @@ -2664,7 +2713,8 @@ async function runNonTtyBuild() { // Main execution const startOrchestrator = async () => { - const orchestratorLockPath = path.resolve('.n-apt-build-orchestrator.lock'); + fs.mkdirSync(appRuntimeDirectory, { recursive: true }); + const orchestratorLockPath = buildOrchestratorLockPath; const releaseOrchestratorLock = await acquireBuildOrchestratorLock(orchestratorLockPath); process.once('exit', releaseOrchestratorLock); diff --git a/scripts/build/buildStatus.ts b/scripts/build/buildStatus.ts index e701caaf..1aedaf2a 100644 --- a/scripts/build/buildStatus.ts +++ b/scripts/build/buildStatus.ts @@ -1,7 +1,7 @@ export type FailingServices = 'Vite' | 'Rust' | 'Redis' | 'WebAssembly'; export interface BuildProcessLike { - status: 'pending' | 'running' | 'success' | 'error'; + status: 'pending' | 'running' | 'success' | 'warning' | 'error'; message?: string; } diff --git a/scripts/build/desktopNotify.ts b/scripts/build/desktopNotify.ts new file mode 100644 index 00000000..64d4c063 --- /dev/null +++ b/scripts/build/desktopNotify.ts @@ -0,0 +1,108 @@ +import { spawn, type ChildProcess } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; + +export type DesktopNotificationOptions = { + title: string; + message: string; + icon?: string; + open?: string; +}; + +const OSASCRIPT_BINARY = '/usr/bin/osascript'; + +export function toAppleScriptString(value: string): string { + return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} + +function isExecutable(candidate: string): boolean { + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + return false; + } +} + +function resolveOnPath(binary: string): string | null { + for (const directory of (process.env.PATH ?? '').split(path.delimiter)) { + if (!directory) continue; + const candidate = path.join(directory, binary); + if (isExecutable(candidate)) return candidate; + } + return null; +} + +function spawnDetached( + binary: string, + args: string[], + onFailure?: () => void +): void { + let child: ChildProcess; + try { + child = spawn(binary, args, { stdio: 'ignore', detached: true }); + } catch { + onFailure?.(); + return; + } + child.on('error', () => onFailure?.()); + child.unref(); +} + +function notifyWithOsascript({ + title, + message +}: DesktopNotificationOptions): void { + const script = `display notification ${toAppleScriptString(message)} with title ${toAppleScriptString(title)}`; + spawnDetached(OSASCRIPT_BINARY, ['-e', script]); +} + +// terminal-notifier stays the richest backend (app icon, sound, click-to-open) +// but node-notifier vendored an x86_64-only copy of it, which stops launching +// once Rosetta is gone. Prefer a native build from PATH, and otherwise defer to +// the banner macOS ships with. +let terminalNotifierBroken = false; + +function notifyWithTerminalNotifier( + binary: string, + options: DesktopNotificationOptions +): void { + const { title, message, icon, open } = options; + const args = ['-title', title, '-message', message]; + if (icon) args.push('-appIcon', icon); + if (open) args.push('-open', open); + + spawnDetached(binary, args, () => { + terminalNotifierBroken = true; + notifyWithOsascript(options); + }); +} + +function notifyWithNotifySend( + binary: string, + { title, message, icon }: DesktopNotificationOptions +): void { + const args: string[] = []; + if (icon) args.push('-i', icon); + args.push(title, message); + spawnDetached(binary, args); +} + +export function notify(options: DesktopNotificationOptions): void { + if (process.platform === 'darwin') { + const terminalNotifier = terminalNotifierBroken + ? null + : resolveOnPath('terminal-notifier'); + if (terminalNotifier) { + notifyWithTerminalNotifier(terminalNotifier, options); + return; + } + notifyWithOsascript(options); + return; + } + + if (process.platform === 'linux') { + const notifySend = resolveOnPath('notify-send'); + if (notifySend) notifyWithNotifySend(notifySend, options); + } +} diff --git a/scripts/build/devStatusServer.ts b/scripts/build/devStatusServer.ts index 11b45435..23f4bf98 100644 --- a/scripts/build/devStatusServer.ts +++ b/scripts/build/devStatusServer.ts @@ -1,6 +1,7 @@ import fs from "node:fs"; import http from "node:http"; import { THEME_TOKENS } from "@n-apt/consts/theme"; +import { rebuildStatusPath } from "./runtimePaths"; export interface DevStatusServerOptions { port?: number; @@ -248,7 +249,7 @@ export function startDevStatusServer( options: DevStatusServerOptions = {}, ): Promise { const port = options.port ?? 5173; - const statusPath = options.statusPath ?? ".rebuild_status.json"; + const statusPath = options.statusPath ?? rebuildStatusPath; return new Promise((resolve) => { const server = http.createServer((req, res) => { diff --git a/scripts/build/runtimePaths.ts b/scripts/build/runtimePaths.ts new file mode 100644 index 00000000..e7c24a76 --- /dev/null +++ b/scripts/build/runtimePaths.ts @@ -0,0 +1,7 @@ +import path from "node:path"; + +export const appRuntimeDirectory = path.resolve(".n-apt"); +export const backendHandoffLockPath = path.join(appRuntimeDirectory, "backend-handoff.lock"); +export const backendTargetPath = path.join(appRuntimeDirectory, "backend-target.json"); +export const buildOrchestratorLockPath = path.join(appRuntimeDirectory, "build-orchestrator.lock"); +export const rebuildStatusPath = path.join(appRuntimeDirectory, "rebuild_status.json"); diff --git a/scripts/build/rustHotReloadGate.ts b/scripts/build/rustHotReloadGate.ts index 1f01672c..c5ea0771 100644 --- a/scripts/build/rustHotReloadGate.ts +++ b/scripts/build/rustHotReloadGate.ts @@ -150,17 +150,6 @@ export function createRustHotReloadGate( }; } -export function buildRustBackendStopCommand( - pid: number, - platform: NodeJS.Platform = process.platform, -): string { - if (platform === "win32") { - return `taskkill /PID ${pid} /T /F`; - } - - return `kill -TERM -${pid} || kill -TERM ${pid}`; -} - export async function runRustHotReloadValidation( deps: RustHotReloadValidationDependencies, ): Promise { diff --git a/scripts/build/rustHotReloadHandoff.ts b/scripts/build/rustHotReloadHandoff.ts new file mode 100644 index 00000000..771b0bbc --- /dev/null +++ b/scripts/build/rustHotReloadHandoff.ts @@ -0,0 +1,281 @@ +/** + * Sequential Rust backend handoff helpers. + * + * A USB SDR is an exclusive resource: only one process can own the interface at + * a time. The hot-reload swap therefore has to stop the current backend and wait + * for it to release the device *before* starting the replacement, otherwise the + * replacement's open attempts collide with the outgoing owner and libusb reports + * `usb_claim_interface error -3` (device busy) / `-99`. + */ + +/** Wait after the previous backend exits before the replacement probes USB. */ +export const HOT_RELOAD_USB_RELEASE_SETTLE_MS = 250; +/** How long to wait for the outgoing backend process to exit. */ +export const HOT_RELOAD_PROCESS_EXIT_TIMEOUT_MS = 5000; +/** How long to wait for the replacement backend to claim a supported radio. */ +export const HOT_RELOAD_SDR_READY_TIMEOUT_MS = 15000; +/** + * Minimum time the replacement backend is polled before an empty source + * inventory is trusted. The backend refreshes its USB inventory on a periodic + * probe, so an empty list immediately after startup is ambiguous rather than + * proof that no radio is attached. + */ +export const HOT_RELOAD_SDR_INVENTORY_SETTLE_MS = 3000; + +/** Source kinds that represent exclusive physical radios, not simulations. */ +export const HARDWARE_SOURCE_KINDS = ["rtl-sdr", "hackrf_one"] as const; + +export type HotReloadDeviceState = { + connected: boolean; + state: string; + info: string; +}; + +export type SdrReadiness = + | { ready: true; outcome: "connected" | "no-hardware" } + | { ready: false; outcome: "waiting" }; + +export type SdrReadyOutcome = "connected" | "no-hardware" | "failed" | "cancelled"; + +export type SdrReadyResult = { + ok: boolean; + outcome: SdrReadyOutcome; + device?: HotReloadDeviceState; + hardwareSourceKinds: string[]; +}; + +export type Sleep = (ms: number) => Promise; + +export type FetchResponseLike = { + ok: boolean; + json: () => Promise; +}; + +export type FetchLike = (url: string) => Promise; + +export function isHardwareSourceKind(kind: string): boolean { + return (HARDWARE_SOURCE_KINDS as readonly string[]).includes(kind); +} + +/** + * Read the source kinds from the backend `/status` snapshot + * (`{ status: { sources: [{ kind }] } }`). + */ +export function extractSourceKinds(snapshot: unknown): string[] { + const sources = (snapshot as { status?: { sources?: unknown } } | null)?.status + ?.sources; + if (!Array.isArray(sources)) return []; + + return sources + .map((source) => (source as { kind?: unknown } | null)?.kind) + .filter((kind): kind is string => typeof kind === "string" && kind.length > 0); +} + +/** + * Read the device block from the backend `/api/agent/status` payload + * (`{ device: { connected, state, info } }`). The flat `/status` route does not + * expose device state. + */ +export function extractDeviceState(payload: unknown): HotReloadDeviceState | null { + const device = (payload as { device?: unknown } | null)?.device; + if (!device || typeof device !== "object") return null; + + const { connected, state, info } = device as Record; + if (typeof connected !== "boolean") return null; + + return { + connected, + state: typeof state === "string" ? state : "", + info: typeof info === "string" ? info : "", + }; +} + +/** + * A reported connection wins outright. When no hardware source has ever been + * enumerated the reload is allowed to proceed, because the operator is running + * without a radio; the caller applies the inventory settle window before + * trusting that. + */ +export function evaluateSdrReadiness( + device: HotReloadDeviceState | null, + hardwareSourceKinds: readonly string[], +): SdrReadiness { + if (device?.connected) { + return { ready: true, outcome: "connected" }; + } + + if (!hardwareSourceKinds.some(isHardwareSourceKind)) { + return { ready: true, outcome: "no-hardware" }; + } + + return { ready: false, outcome: "waiting" }; +} + +export function formatSdrReadyFailure(result: SdrReadyResult): string { + const kinds = result.hardwareSourceKinds.filter(isHardwareSourceKind); + const detected = kinds.length > 0 ? kinds.join(", ") : "unknown radio"; + const state = result.device?.state ? `state=${result.device.state}` : "state unknown"; + const info = result.device?.info ? `, ${result.device.info}` : ""; + return `${detected} detected but never connected (${state}${info})`; +} + +const defaultSleep: Sleep = (ms) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); + +const defaultFetch: FetchLike = (url) => fetch(url, { cache: "no-store" }); + +async function fetchJson(fetchImpl: FetchLike, url: string): Promise { + try { + const response = await fetchImpl(url); + if (!response.ok) return null; + return await response.json(); + } catch { + return null; + } +} + +/** + * Poll a freshly started backend until it has claimed a supported radio, or + * until the source inventory proves that none is attached. + * + * Observed hardware kinds are accumulated across polls: a radio that is + * enumerated once must not be forgotten while the inventory churns. + */ +export async function waitForSdrReady(options: { + baseUrl: string; + timeoutMs?: number; + pollMs?: number; + settleMs?: number; + fetchImpl?: FetchLike; + sleep?: Sleep; + now?: () => number; + isCancelled?: () => boolean; +}): Promise { + const { + baseUrl, + timeoutMs = HOT_RELOAD_SDR_READY_TIMEOUT_MS, + pollMs = 250, + settleMs = HOT_RELOAD_SDR_INVENTORY_SETTLE_MS, + fetchImpl = defaultFetch, + sleep = defaultSleep, + now = Date.now, + isCancelled, + } = options; + + const startedAt = now(); + const observedKinds = new Set(); + let lastDevice: HotReloadDeviceState | null = null; + let sawDevicePayload = false; + + for (;;) { + if (isCancelled?.()) { + return { + ok: false, + outcome: "cancelled", + device: lastDevice ?? undefined, + hardwareSourceKinds: Array.from(observedKinds), + }; + } + + const [devicePayload, sourceSnapshot] = await Promise.all([ + fetchJson(fetchImpl, `${baseUrl}/api/agent/status`), + fetchJson(fetchImpl, `${baseUrl}/status`), + ]); + + const device = extractDeviceState(devicePayload); + if (device) { + lastDevice = device; + sawDevicePayload = true; + } + for (const kind of extractSourceKinds(sourceSnapshot)) { + observedKinds.add(kind); + } + + const hardwareSourceKinds = Array.from(observedKinds); + const readiness = evaluateSdrReadiness(lastDevice, hardwareSourceKinds); + + if (readiness.ready) { + // A reported connection is authoritative and returns immediately. An + // absent-radio pass additionally requires the backend to have answered + // with device state and the inventory to have settled; otherwise a + // backend that is still starting (or unreachable) would masquerade as a + // machine with no radio attached. + const settled = sawDevicePayload && now() - startedAt >= settleMs; + if (readiness.outcome === "connected" || settled) { + return { + ok: true, + outcome: readiness.outcome, + device: lastDevice ?? undefined, + hardwareSourceKinds, + }; + } + } + + if (now() - startedAt >= timeoutMs) { + return { + ok: false, + outcome: "failed", + device: lastDevice ?? undefined, + hardwareSourceKinds, + }; + } + + await sleep(pollMs); + } +} + +function defaultIsProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (error) { + // ESRCH means the process is gone. EPERM means it exists but belongs to + // another user, which still counts as alive for handoff purposes. + return (error as NodeJS.ErrnoException | undefined)?.code === "EPERM"; + } +} + +/** Wait until a pid is gone, returning false if it outlives the timeout. */ +export async function waitForProcessExit(options: { + pid: number; + timeoutMs?: number; + pollMs?: number; + isAlive?: (pid: number) => boolean; + sleep?: Sleep; + now?: () => number; +}): Promise { + const { + pid, + timeoutMs = HOT_RELOAD_PROCESS_EXIT_TIMEOUT_MS, + pollMs = 100, + isAlive = defaultIsProcessAlive, + sleep = defaultSleep, + now = Date.now, + } = options; + + const deadline = now() + timeoutMs; + while (now() < deadline) { + if (!isAlive(pid)) return true; + await sleep(pollMs); + } + + return !isAlive(pid); +} + +/** + * Signal a detached child's whole process group, falling back to the pid alone + * when the group is already gone. + */ +export function signalProcessTree(pid: number, signal: NodeJS.Signals): void { + try { + process.kill(-pid, signal); + } catch { + try { + process.kill(pid, signal); + } catch { + // Already exited; nothing left to signal. + } + } +} diff --git a/scripts/build/start-webusb-dev.ts b/scripts/build/start-webusb-dev.ts index d5bec100..c9733b68 100644 --- a/scripts/build/start-webusb-dev.ts +++ b/scripts/build/start-webusb-dev.ts @@ -3,13 +3,11 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { spawn, spawnSync } from "node:child_process"; import { isProjectBuildOrchestratorCommand } from "./webusbDevOwnership"; +import { buildOrchestratorLockPath } from "./runtimePaths"; const scriptDirectory = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(scriptDirectory, "../.."); -const orchestratorLockPath = path.join( - projectRoot, - ".n-apt-build-orchestrator.lock", -); +const orchestratorLockPath = buildOrchestratorLockPath; const orchestratorScriptPath = path.join( scriptDirectory, "build-orchestrator.tsx", @@ -27,8 +25,9 @@ function readLock(): OrchestratorLock | null { const parsed = JSON.parse( fs.readFileSync(orchestratorLockPath, "utf8"), ) as Partial; - if (!Number.isInteger(parsed.pid) || (parsed.pid ?? 0) <= 0) return null; - return { pid: parsed.pid, startedAt: parsed.startedAt }; + const pid = parsed.pid; + if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null; + return { pid, startedAt: parsed.startedAt }; } catch { return null; } diff --git a/scripts/data/cleanEntities.cjs b/scripts/data/cleanEntities.cjs index 67540bdc..1dcfe017 100644 --- a/scripts/data/cleanEntities.cjs +++ b/scripts/data/cleanEntities.cjs @@ -193,7 +193,7 @@ class EntityValidator { } // Check if it's a full state name - const stateCode = Array.from(this.US_STATES.entries()).find(([_, name]) => name === cleanedState)?.[0]; + const stateCode = Array.from(this.US_STATES.entries()).find(([, name]) => name === cleanedState)?.[0]; if (stateCode) { return { isValid: true, diff --git a/scripts/lint/check-entry-reachability.mjs b/scripts/lint/check-entry-reachability.mjs index 820098a6..1b1698df 100644 --- a/scripts/lint/check-entry-reachability.mjs +++ b/scripts/lint/check-entry-reachability.mjs @@ -145,6 +145,20 @@ const scriptText = JSON.stringify(pkg.dependencies || {}) + JSON.stringify(pkg.devDependencies || {}); +// A package.json script that runs a repo file is a real entry point: the CLI is +// invoked by hand rather than imported from an app route. Without seeding these, +// modules consumed only by scripts/ look unreachable and get deleted. +const scriptEntryFiles = new Set(); +const scriptTargetRe = /(scripts\/[A-Za-z0-9_/.-]+\.(?:tsx?|mjs|cjs|js))/g; +for (const cmd of Object.values(pkg.scripts || {})) { + for (const mm of cmd.matchAll(scriptTargetRe)) { + const target = path.normalize(mm[1]); + if (!/\.(ts|tsx)$/.test(target) || !fs.existsSync(target)) continue; + scriptEntryFiles.add(target); + if (!files.includes(target)) files.push(target); + } +} + const prodSeeds = new Set( files.filter( (f) => @@ -157,6 +171,7 @@ const prodSeeds = new Set( scriptText.includes(path.basename(f, path.extname(f)))), ), ); +for (const f of scriptEntryFiles) prodSeeds.add(f); for (const f of prodSeeds) { routeRefs(f).forEach((r) => prodSeeds.add(r)); } diff --git a/src/app-article/App.tsx b/src/app-article/App.tsx index 99fb9493..5d7c0eae 100644 --- a/src/app-article/App.tsx +++ b/src/app-article/App.tsx @@ -250,6 +250,13 @@ const App: React.FC = () => { ); return hasBlockElement ? <>{children} :

; }, + h3: ({ node: _node, children, ...props }) => { + const headingText = React.Children.toArray(children).join("").trim(); + const className = headingText === "How long have I been trapped?" + ? "days-since-heading" + : undefined; + return

{children}

; + }, img: ({ node: _node, ...props }) => , "latex-block": ({ node: _node, ...props }: any) => , "body-attenuation-canvas": ({ node: _node, ...props }: any) => }> , @@ -437,6 +444,10 @@ const ArticleContent = styled.article` font-size: clamp(1.3rem, 3vw, 1.8rem); } + .days-since-heading { + margin: 0; + } + .dropcap { font-family: "JetBrains Mono", monospace; font-size: clamp(3.5rem, 8vw, 4.5rem); diff --git a/src/rs/crypto/mod.rs b/src/rs/crypto/mod.rs index cc5d0b9c..cc5685f4 100644 --- a/src/rs/crypto/mod.rs +++ b/src/rs/crypto/mod.rs @@ -28,7 +28,10 @@ pub fn get_pbkdf2_salt() -> &'static [u8] { /// Derive a 256-bit AES key from a passkey using PBKDF2-HMAC-SHA256. pub fn derive_key(passkey: &str) -> [u8; 32] { - let mut key = [0u8; 32]; + // PBKDF2 fills the complete output buffer before it is returned. Using the + // type's default value here avoids presenting an all-zero array as a key to + // static analyzers; the initialized bytes never leave this function. + let mut key = [Default::default(); 32]; let trimmed = passkey.trim(); pbkdf2_hmac::( trimmed.as_bytes(), @@ -75,10 +78,11 @@ pub fn encrypt_payload_binary( .map_err(|e| anyhow!("cipher init: {e}"))?; let iv_bytes: [u8; 12] = ::rand::random(); - let nonce = Nonce::from_slice(&iv_bytes); + let nonce = + Nonce::try_from(&iv_bytes[..]).map_err(|e| anyhow!("nonce init: {e}"))?; let ciphertext = cipher - .encrypt(nonce, plaintext) + .encrypt(&nonce, plaintext) .map_err(|e| anyhow!("encrypt: {e}"))?; // Wire format: IV || ciphertext (which includes the GCM tag) @@ -110,10 +114,11 @@ pub fn decrypt_payload_binary( .map_err(|e| anyhow!("cipher init: {e}"))?; let (iv_bytes, ciphertext) = payload.split_at(12); - let nonce = Nonce::from_slice(iv_bytes); + let nonce = + Nonce::try_from(iv_bytes).map_err(|e| anyhow!("nonce init: {e}"))?; let plaintext = cipher - .decrypt(nonce, ciphertext) + .decrypt(&nonce, ciphertext) .map_err(|e| anyhow!("decrypt: {e}"))?; Ok(plaintext) diff --git a/src/rs/sdr/hackrf/device.rs b/src/rs/sdr/hackrf/device.rs index 7cfa2d01..b5aacd77 100644 --- a/src/rs/sdr/hackrf/device.rs +++ b/src/rs/sdr/hackrf/device.rs @@ -92,6 +92,10 @@ pub struct HackRfDevice { audio_tap: AudioIqTap, streaming_started: bool, serial_number: String, + /// True while this handle owns the process-wide hackrf session opened by + /// `hackrf_init`. Guards the exit so an explicit `cleanup()` followed by the + /// later `Drop` cannot tear the native session down twice. + native_session_active: bool, } unsafe impl Send for HackRfDevice {} @@ -250,6 +254,7 @@ impl HackRfDevice { audio_tap: AudioIqTap::new(), streaming_started: false, serial_number, + native_session_active: true, }) } } @@ -343,7 +348,10 @@ impl HackRfDevice { } let _ = self.rx_context.take(); let _ = self.tx_context.take(); - let _ = unsafe { ffi::hackrf_exit() }; + if self.native_session_active { + self.native_session_active = false; + let _ = unsafe { ffi::hackrf_exit() }; + } } } @@ -541,26 +549,14 @@ impl SdrDevice for HackRfDevice { } fn set_center_frequency_live(&mut self, freq: u32) -> Result<()> { - let was_streaming = self.streaming_started; - if !was_streaming { - return self.set_center_frequency(freq); - } - - // HackRF's native frequency setter is not a safe in-flight RX control - // operation. Stop the native stream before changing the LO, then restart - // it before returning to the acquisition worker. A failed tune still - // attempts to restore RX so one bad channel selection cannot strand the - // device in a frozen state. - self.stop_streaming(); + // hackrf_set_freq is a synchronous control transfer and stays valid while + // RX is streaming. Stopping and restarting the native stream here + // (hackrf_stop_rx can block in firmware) turned every VFO tick into a full + // stream restart and stalled scrolling. Retune in place, then drop chunks + // already buffered at the old LO so the next frame is new-frequency content. + self.set_center_frequency(freq)?; self.flush_read_queue(); - let tune_result = self.set_center_frequency(freq); - let restart_result = self.ensure_streaming(); - - match (tune_result, restart_result) { - (Err(error), _) => Err(error), - (Ok(()), Err(error)) => Err(error), - (Ok(()), Ok(())) => Ok(()), - } + Ok(()) } fn set_gain(&mut self, gain: f64) -> Result<()> { @@ -788,6 +784,7 @@ mod tests { tx_started: false, streaming_started: false, serial_number: String::new(), + native_session_active: false, }; let first_ptr = device.rx_context_ptr(); @@ -797,6 +794,35 @@ mod tests { assert!(device.rx_context.is_some()); } + #[test] + fn repeated_cleanup_does_not_reenter_the_native_session() { + // The shutdown path calls `cleanup()` explicitly and `Drop` calls it again. + // The second pass must not tear the hackrf session down twice. + let (_tx, rx) = bounded::>(HACKRF_RX_QUEUE_DEPTH); + let mut device = HackRfDevice { + dev: ptr::null_mut(), + rx_queue: rx, + rx_context: None, + last_error: None, + sample_rate: HACKRF_MIN_SAMPLE_RATE, + center_frequency: 0, + requested_center_frequency: 0, + ppm: 0, + iq_buffer: Vec::new(), + audio_tap: AudioIqTap::new(), + tx_context: None, + tx_started: false, + streaming_started: false, + serial_number: String::new(), + native_session_active: false, + }; + + assert!(device.cleanup().is_ok()); + assert!(device.cleanup().is_ok()); + assert!(!device.native_session_active); + assert!(device.dev.is_null()); + } + #[test] fn warm_rx_queue_is_drained_before_display_resumes() { let (tx, rx) = bounded::>(HACKRF_RX_QUEUE_DEPTH); diff --git a/src/rs/sdr/processor/mod.rs b/src/rs/sdr/processor/mod.rs index 9eabaa77..165b9968 100644 --- a/src/rs/sdr/processor/mod.rs +++ b/src/rs/sdr/processor/mod.rs @@ -1319,7 +1319,11 @@ impl SdrProcessor { self.device.set_direct_sampling(direct_sampling)?; } - if let Some(bandwidth) = settings.tuner_bandwidth { + // A zero width is not a valid hardware filter (the MAX2837 has discrete + // widths starting at 1.75 MHz): it is the UI's "resume auto" sentinel. + // Forwarding it to the device fails the retune and tears the stream down, + // so treat it as "leave the filter alone". + if let Some(bandwidth) = settings.tuner_bandwidth.filter(|bw| *bw > 0) { self.device.set_tuner_bandwidth(bandwidth)?; } @@ -2372,7 +2376,7 @@ mod hackrf_settings_tests { } #[test] - fn apply_settings_routes_disabled_tuner_bandwidth_to_hardware_calls() { + fn apply_settings_skips_a_zero_tuner_bandwidth_instead_of_touching_the_device() { let device = RecordingDevice::default(); let calls = device.calls.clone(); let mut processor = @@ -2387,10 +2391,9 @@ mod hackrf_settings_tests { }) .expect("apply settings"); - assert_eq!( - *calls.lock().unwrap(), - vec!["tuner_bandwidth:0".to_string(),], - ); + // Zero is the UI's "resume auto" sentinel, not a hardware width. Writing it + // would fail the MAX2837 filter set and tear the live stream down. + assert!(calls.lock().unwrap().is_empty()); } #[test] diff --git a/src/rs/server/iq_format.rs b/src/rs/server/iq_format.rs index b2574a64..5fc31d9e 100644 --- a/src/rs/server/iq_format.rs +++ b/src/rs/server/iq_format.rs @@ -1,9 +1,11 @@ use serde::{Deserialize, Serialize}; +use sha2::Digest; const MAGIC: &[u8; 8] = b"NAPT-IQ3"; const HEADER_SIZE: usize = 40; const TRAILER_MAGIC: &[u8; 8] = b"NAPTTRLR"; const TRAILER_HEADER_SIZE: usize = 24; +const INTEGRITY_PLACEHOLDER: &str = "0000000000000000000000000000000000000000000000000000000000000000"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IqMetadata { @@ -69,7 +71,7 @@ pub fn encode( file: &IqFile, key: Option<&[u8; 32]>, ) -> Result, String> { - encode_versioned(file, key, 4) + encode_versioned(file, key, 5) } fn encode_versioned( @@ -110,12 +112,18 @@ fn encode_versioned( .map_err(|e| format!("IQ encryption failed: {e}"))?; } let trailer_json = if version >= 4 { - serde_json::to_vec( - &file - .trailer - .clone() - .unwrap_or_else(|| serde_json::json!({})), - ) + let mut trailer = file + .trailer + .clone() + .unwrap_or_else(|| serde_json::json!({})); + if version >= 5 { + trailer["integrity"] = serde_json::json!({ + "algorithm": "SHA-256", + "scope": "file-with-integrity-digest-placeholder", + "digest": INTEGRITY_PLACEHOLDER, + }); + } + serde_json::to_vec(&trailer) .map_err(|e| e.to_string())? } else { Vec::new() @@ -134,7 +142,7 @@ fn encode_versioned( "sections".into(), serde_json::json!({ "binary": { "offset_bytes": binary_offset, "length_bytes": payload.len(), "encoding": "iq_u8_interleaved", "encrypted": key.is_some() }, - "trailer": { "offset_bytes": trailer_offset, "length_bytes": trailer_len, "encoding": "utf8_json", "version": 1 } + "trailer": { "offset_bytes": trailer_offset, "length_bytes": trailer_len, "encoding": "utf8_json", "version": if version >= 5 { 2 } else { 1 } } }), ); } @@ -162,11 +170,19 @@ fn encode_versioned( out.extend_from_slice(&payload); if version >= 4 { out.extend_from_slice(TRAILER_MAGIC); - out.push(1); + out.push(if version >= 5 { 2 } else { 1 }); out.extend_from_slice(&[0; 7]); out.extend_from_slice(&(trailer_json.len() as u64).to_le_bytes()); out.extend_from_slice(&trailer_json); } + if version >= 5 { + let digest = sha2::Sha256::digest(&out); + let digest = digest.iter().map(|byte| format!("{byte:02x}")).collect::(); + let placeholder = INTEGRITY_PLACEHOLDER.as_bytes(); + let offset = out.windows(placeholder.len()).position(|window| window == placeholder) + .ok_or("missing IQ integrity placeholder")?; + out[offset..offset + placeholder.len()].copy_from_slice(digest.as_bytes()); + } Ok(out) } @@ -395,9 +411,16 @@ mod tests { let encoded = encode(&file, None).expect("encode v4 IQ"); let decoded = decode(&encoded, None).expect("decode v4 IQ"); - assert_eq!(decoded.metadata.format_version, 4); + assert_eq!(decoded.metadata.format_version, 5); assert_eq!(decoded.chunks[0].data, vec![1, 2, 3, 4]); - assert_eq!(decoded.trailer, file.trailer); + assert_eq!( + decoded.trailer.as_ref().and_then(|trailer| trailer["processing"].as_object()), + file.trailer.as_ref().and_then(|trailer| trailer["processing"].as_object()) + ); + assert_eq!( + decoded.trailer.as_ref().and_then(|trailer| trailer["integrity"]["algorithm"].as_str()), + Some("SHA-256") + ); let sections = decoded .metadata diff --git a/src/rs/server/main.rs b/src/rs/server/main.rs index 1f4170ee..8021bd94 100644 --- a/src/rs/server/main.rs +++ b/src/rs/server/main.rs @@ -502,6 +502,16 @@ impl websocket_server::WebSocketServer { while !shutdown_state.shutdown.load(Ordering::Relaxed) { tokio::time::sleep(Duration::from_millis(50)).await; } + // axum completing here lets run_server return and the process exit. The + // SDR I/O thread owns the exclusive USB device, so wait (bounded) until it + // has released it: exiting first leaves a HackRF still streaming and the + // replacement backend cannot claim the interface. + let deadline = tokio::time::Instant::now() + Duration::from_secs(3); + while !shutdown_state.device_released() + && tokio::time::Instant::now() < deadline + { + tokio::time::sleep(Duration::from_millis(25)).await; + } }; let readiness_state = websocket_server.get_shared_state(); @@ -548,14 +558,14 @@ pub async fn run_server() -> Result<()> { let _broadcast_tx = websocket_server.get_broadcast_tx(); // Install signal handler: on shutdown signals, signal the I/O thread to - // shut down so it can release the RTL-SDR device cleanly before exit. + // shut down so it can release the SDR device cleanly before exit. The HTTP + // server's graceful-shutdown future waits for that release before the + // process is allowed to exit. let shutdown_shared = shared.clone(); tokio::spawn(async move { let signal_name = wait_for_shutdown_signal().await; info!("{}", shutdown_signal_received_message(signal_name)); shutdown_shared.shutdown.store(true, Ordering::Relaxed); - // Give the I/O thread time to observe the shutdown flag and unwind. - tokio::time::sleep(Duration::from_millis(250)).await; info!("{}", shutdown_signal_propagated_message(signal_name)); }); diff --git a/src/rs/server/shared_state.rs b/src/rs/server/shared_state.rs index 4e4aa76f..3450173e 100644 --- a/src/rs/server/shared_state.rs +++ b/src/rs/server/shared_state.rs @@ -98,6 +98,10 @@ pub struct SharedState { pub pending_center_freq_notify: Notify, /// Shutdown signal — I/O thread checks this each iteration pub shutdown: AtomicBool, + /// Set by the I/O thread once the exclusive USB SDR has been released. + /// Shutdown must not complete before this, or the process can exit while a + /// HackRF is still streaming and the next backend cannot claim it. + pub device_released: AtomicBool, /// Device info string (set once at init) pub device_info: Mutex, /// USB serial number of the active SDR device @@ -257,6 +261,7 @@ impl SharedState { pending_center_freq_dirty: AtomicBool::new(false), pending_center_freq_notify: Notify::new(), shutdown: AtomicBool::new(false), + device_released: AtomicBool::new(false), device_info: Mutex::new(String::new()), device_serial: Mutex::new(String::new()), device_manufacturer: Mutex::new(String::new()), @@ -580,6 +585,15 @@ impl SharedState { *self.device_backend_error.lock().unwrap() = error; } + /// Record that the exclusive USB SDR is no longer owned by this process. + pub fn mark_device_released(&self) { + self.device_released.store(true, Ordering::Release); + } + + pub fn device_released(&self) -> bool { + self.device_released.load(Ordering::Acquire) + } + pub fn set_rtl_sdr_inventory(&self, inventory: Vec) { *self.rtl_sdr_inventory.lock().unwrap() = inventory; } diff --git a/src/rs/server/utils.rs b/src/rs/server/utils.rs index 61d79855..315bf5f9 100644 --- a/src/rs/server/utils.rs +++ b/src/rs/server/utils.rs @@ -1755,6 +1755,10 @@ impl HashingWriter { .collect::(); (checksum, self.bytes_written) } + + fn finalize_with_inner(self) -> (W, u64) { + (self.inner, self.bytes_written) + } } impl std::io::Write for HashingWriter { @@ -1828,7 +1832,7 @@ pub fn save_capture_file_multi( "data_format": "iq_u8", "spectrum_shifted": true, "format": if result.file_type == ".iq" { "iq" } else if result.file_type == ".wav" { "wav" } else { "napt" }, - "format_version": if result.file_type == ".wav" { 3 } else { 4 }, + "format_version": if result.file_type == ".wav" { 3 } else { 5 }, "interleaving": "IQ", "device_profile": { "kind": result.source_device, @@ -1978,6 +1982,11 @@ pub fn save_capture_file_multi( let trailer_json = serde_json::json!({ "processing": { "operation": "capture" }, "tool_version": env!("CARGO_PKG_VERSION"), + "integrity": { + "algorithm": "SHA-256", + "scope": "file-with-integrity-digest-placeholder", + "digest": "0000000000000000000000000000000000000000000000000000000000000000" + } }) .to_string(); let encrypted_data = @@ -2012,7 +2021,7 @@ pub fn save_capture_file_multi( "offset_bytes": trailer_offset, "length_bytes": trailer_length, "encoding": "utf8_json", - "version": 1 + "version": 2 } }); @@ -2024,9 +2033,7 @@ pub fn save_capture_file_multi( header_size = needed; }; - let file = std::fs::File::create(&path) - .map_err(|e| format!("Failed to create file: {}", e))?; - let mut writer = HashingWriter::new(file); + let mut writer = HashingWriter::new(Vec::::new()); // Write the plaintext JSON header writer @@ -2049,7 +2056,7 @@ pub fn save_capture_file_multi( .map_err(|e| format!("Failed to write encrypted data: {}", e))?; writer.write_all(TRAILER_MAGIC).map_err(|e| e.to_string())?; - writer.write_all(&[1u8; 1]).map_err(|e| e.to_string())?; + writer.write_all(&[2u8; 1]).map_err(|e| e.to_string())?; writer.write_all(&[0u8; 7]).map_err(|e| e.to_string())?; writer .write_all(&(trailer_json.len() as u64).to_le_bytes()) @@ -2062,7 +2069,20 @@ pub fn save_capture_file_multi( .flush() .map_err(|e| format!("Failed to flush file: {}", e))?; - let (checksum, file_size) = writer.finalize(); + let (mut encoded, _) = writer.finalize_with_inner(); + let placeholder = b"0000000000000000000000000000000000000000000000000000000000000000"; + let offset = encoded.windows(placeholder.len()).position(|window| window == placeholder) + .ok_or("Missing capture integrity placeholder")?; + let digest = sha2::Sha256::digest(&encoded); + let digest = digest.iter().map(|byte| format!("{byte:02x}")).collect::(); + encoded[offset..offset + placeholder.len()].copy_from_slice(digest.as_bytes()); + let checksum = sha2::Sha256::digest(&encoded) + .iter() + .map(|byte| format!("{byte:02x}")) + .collect::(); + let file_size = encoded.len() as u64; + std::fs::write(&path, &encoded) + .map_err(|e| format!("Failed to write encrypted capture: {}", e))?; info!( "Saved encrypted capture: {} ({} bytes, sha256:{})", @@ -2765,7 +2785,7 @@ mod dynamic_header_tests { .expect("header newline"); let header: serde_json::Value = serde_json::from_slice(&file[..newline]).expect("parse header json"); - assert_eq!(header["metadata"]["format_version"], 4); + assert_eq!(header["metadata"]["format_version"], 5); // Header size is a 1024-multiple >= 4096 that covers the JSON + newline. let binary_offset = header["metadata"]["sections"]["binary"]["offset_bytes"] diff --git a/src/rs/server/websocket_server/mod.rs b/src/rs/server/websocket_server/mod.rs index 0ced5c2d..5b60886c 100644 --- a/src/rs/server/websocket_server/mod.rs +++ b/src/rs/server/websocket_server/mod.rs @@ -1143,6 +1143,9 @@ impl WebSocketServer { self .shared_state .set_device_backend_error(Some(error.to_string())); + // Hardware was never handed to this process, so shutdown must not wait + // on a release that will never happen. + self.shared_state.mark_device_released(); return Err(error); } @@ -1656,6 +1659,18 @@ impl WebSocketServer { } self.source_runtime_manager.stop_all(); + // Release the exclusive USB SDR before teardown. hackrf_close() stops the + // native stream and tells the firmware to stop; without it the device keeps + // streaming and the next backend cannot claim the interface + // (usb_claim_interface error -3/-99). HackRfDevice::cleanup() is idempotent, + // so the later Drop cannot double-exit the native session. + { + let mut processor = sdr_processor.lock().await; + if let Err(error) = processor.cleanup() { + warn!("Failed to release SDR during shutdown: {}", error); + } + } + self.shared_state.mark_device_released(); tx_monitor_task.abort(); let _ = tx_monitor_task.await; Ok(()) diff --git a/src/rs/server/websocket_server/sources.rs b/src/rs/server/websocket_server/sources.rs index ab6f392c..2e45efa8 100644 --- a/src/rs/server/websocket_server/sources.rs +++ b/src/rs/server/websocket_server/sources.rs @@ -598,6 +598,7 @@ fn build_source_payload( "id": source_id, "name": name, "kind": kind, + "is_mock": kind.starts_with("mock"), "capability": source_capability_for_kind_and_duplex(kind, duplex_mode), "duplex_mode": duplex_mode, "status": source_status_for_entry( diff --git a/src/ts/app/infrastructure/io/filePlaceholderState.ts b/src/ts/app/infrastructure/io/filePlaceholderState.ts index 5e0793d9..81dedd3d 100644 --- a/src/ts/app/infrastructure/io/filePlaceholderState.ts +++ b/src/ts/app/infrastructure/io/filePlaceholderState.ts @@ -35,6 +35,14 @@ export const getFilePlaceholderState = ({ !/success|processed|stitched/i.test(normalizedStatus) && !/no files selected/i.test(normalizedStatus) ) { + if (/integrity|corrupt|modified/i.test(normalizedStatus)) { + return { + kind: "error", + reason: "File integrity failed", + title: "File appears corrupted or modified", + message: "Its contents do not match the integrity record.", + }; + } return { kind: "error", reason: "File processing error", diff --git a/src/ts/app/infrastructure/streams/sourceModeManagement.ts b/src/ts/app/infrastructure/streams/sourceModeManagement.ts index d2461edf..3e7e3193 100644 --- a/src/ts/app/infrastructure/streams/sourceModeManagement.ts +++ b/src/ts/app/infrastructure/streams/sourceModeManagement.ts @@ -10,7 +10,6 @@ export type SourceViewMode = "rx" | "tx"; export type SourceModeAction = | "clear_tx_binding" | "pause_rx" - | "resume_rx" | "bind_tx" | "enter_tx_standby" | "request_rx_mode" @@ -265,13 +264,17 @@ export const resolveSourceModeTransition = ({ } if (toMode === "rx") { + // A half-duplex device stopped Rx to transmit, so returning to Rx lands on + // a held pause: the client owns that latch and surfaces it as "Resume Rx" + // plus the paused banner. Resuming here instead paints a live-looking Rx + // over a stream the device has not restarted yet. return { sourceId, fromMode, toMode, actions: [ "clear_tx_binding", - ...(duplexMode === "half_duplex" ? ["resume_rx" as const] : []), + ...(duplexMode === "half_duplex" ? ["pause_rx" as const] : []), "request_rx_mode", "request_rx_frame", ], diff --git a/src/ts/app/routes/pages/SpectrumRoute.tsx b/src/ts/app/routes/pages/SpectrumRoute.tsx index 1fef7456..b2365999 100644 --- a/src/ts/app/routes/pages/SpectrumRoute.tsx +++ b/src/ts/app/routes/pages/SpectrumRoute.tsx @@ -77,6 +77,7 @@ import { selectTxHopChannels } from "@n-apt/redux/selectors/spectrumSelectors"; import { clampFrequencyRangeToBounds, buildCenteredFrequencyRange, + getAvailableSpectrumBounds, normalizeFrequencyRangeToHz, resolveCenteredFrequencyHz, } from "@n-apt/math/frequency"; @@ -87,19 +88,16 @@ import { } from "@n-apt/math/basebandMirror"; export const resolveNavigationFrequencyBounds = ({ - channelBounds, hardwareBounds, }: { - channelBounds: FrequencyRange | null; hardwareBounds: FrequencyRange | null; -}): FrequencyRange | null => { +}): FrequencyRange => { // The live VFO is a device acquisition control, not a subscriber-local - // channel selector. Using channelBounds at 1x made zoom level decide whether - // 0 Hz was reachable, so two clients could show the same center with - // different lower edges. Keep the device bounds at every zoom level; the - // channel is still used when hardware bounds have not hydrated yet. - if (hardwareBounds) return hardwareBounds; - return channelBounds; + // channel selector. The active channel must never bound navigation: falling + // back to channelBounds trapped scrolling inside the selected channel + // whenever the hardware range had not hydrated. Use the device bounds when + // known, otherwise the global spectrum bounds the backend advertises. + return hardwareBounds ?? getAvailableSpectrumBounds(null); }; export const isWholeChannelPan = ({ @@ -231,8 +229,11 @@ export const createLiveFrequencyRangePublisher = ( }, equals: (left, right) => left.min === right.min && left.max === right.max, - intervalMs: 50, - idleFlushMs: 80, + // One display frame. The frame rate governs scroll responsiveness; a coarser + // cadence made scrolling feel bogged down, and the backend already coalesces + // retunes to the latest value. + intervalMs: 16, + idleFlushMs: 16, }); return { @@ -664,9 +665,9 @@ export const SpectrumRoute: React.FC = ({ source: "user-pan" | "mode-enter" | "typed" = "user-pan", ) => { // The canvas keeps the live view in refs while a gesture is in flight. - // Coalesce Redux and device updates to one latest-value publish every - // 50 ms. The interaction hook already updates the live refs and asks - // the canvas to repaint synchronously, so this does not slow the drag. + // Coalesce Redux and device updates to one latest-value publish per + // display frame. The interaction hook already updates the live refs and + // asks the canvas to repaint synchronously, so this does not slow the drag. const publisher = liveFrequencyRangePublisherRef.current; if (source === "user-pan") { if (publisher) { @@ -840,8 +841,12 @@ export const SpectrumRoute: React.FC = ({ transportSourceId: sourceTransport?.sourceId ?? null, transportPhase: sourceTransport?.phase ?? "idle", }) || isSelectedTxPreviewStandby; + // A `standby` report is only Tx chrome while the source is actually bound to + // the Tx suite. A lingering standby snapshot after an Rx handoff is an Rx + // presentation, and painting the Tx bar there would mask the paused banner. const isSelectedSourceTxStandby = - selectedSourceStatus === "standby" || selectedSource?.status === "standby"; + (selectedSourceStatus === "standby" || selectedSource?.status === "standby") && + (isSelectedMockTxSource || txSuiteSourceId === selectedSourceId); const isSelectedSourceTxStatus = isSelectedSourceTxStandby || isSelectedTxPreviewStandby || @@ -1476,7 +1481,6 @@ export const SpectrumRoute: React.FC = ({ } const primaryBounds = resolveNavigationFrequencyBounds({ - channelBounds: activeSignalAreaBounds, hardwareBounds: hardwareSpectrumBounds, }); const isWholeChannel = isWholeChannelPan({ @@ -1484,7 +1488,7 @@ export const SpectrumRoute: React.FC = ({ channelBounds: activeSignalAreaBounds, }); const clampedRange = normalizeFrequencyRangeToHz( - primaryBounds && !isWholeChannel + !isWholeChannel ? clampFrequencyRangeToBounds(range, primaryBounds) : range, ); diff --git a/src/ts/app/routes/pages/spectrum/hooks/useLiveTuning.ts b/src/ts/app/routes/pages/spectrum/hooks/useLiveTuning.ts index 11087f86..ca235a35 100644 --- a/src/ts/app/routes/pages/spectrum/hooks/useLiveTuning.ts +++ b/src/ts/app/routes/pages/spectrum/hooks/useLiveTuning.ts @@ -189,13 +189,10 @@ export const useFrequencyTuning = (options: UseFrequencyTuningOptions) => { } const primaryBounds = resolveNavigationFrequencyBounds({ - channelBounds: activeSignalAreaBounds, hardwareBounds: hardwareSpectrumBounds, }); const clampedRange = normalizeFrequencyRangeToHz( - primaryBounds - ? clampFrequencyRangeToBounds(range, primaryBounds) - : range, + clampFrequencyRangeToBounds(range, primaryBounds), ); publishFrequencyRange(clampedRange, source); applyTxMonitorForRange(clampedRange, source); diff --git a/src/ts/consts/schemas/websocket.ts b/src/ts/consts/schemas/websocket.ts index c2ef7a0d..dac7de41 100644 --- a/src/ts/consts/schemas/websocket.ts +++ b/src/ts/consts/schemas/websocket.ts @@ -377,6 +377,7 @@ export interface SourceInfo { id: string; name: string; kind: string; + is_mock?: boolean; capability: SourceCapability; duplex_mode?: string | null; /** Backend-reported hardware mode; view mode remains UI-owned. */ diff --git a/src/ts/features/capture/policy.ts b/src/ts/features/capture/policy.ts new file mode 100644 index 00000000..e64ddd68 --- /dev/null +++ b/src/ts/features/capture/policy.ts @@ -0,0 +1,199 @@ +import type { SourceInfo } from "@n-apt/consts/schemas/websocket"; + +export type CaptureSource = Pick< + SourceInfo, + "id" | "kind" | "capability" | "status" +>; + +export interface ChannelCapability { + min: number; + max: number; +} + +export interface CaptureCapabilities { + channels: Record; + sampleRateHz: number; +} + +export interface CaptureTarget { + channel?: string; + minHz: number; + maxHz: number; + centerFrequencyHz: number; +} + +export interface NaptReceiveDefaults { + gainDb: number; + ppm: number; +} + +/** Default FFT size for every headless CLI capture workflow. */ +export const CLI_CAPTURE_DEFAULT_FFT_SIZE = 65_536; + +/** + * Resolves the FFT size shared by snapshot and I/Q CLI captures. Keeping this + * outside the CLI entry point prevents the two workflows from drifting apart. + */ +export function resolveCliCaptureFftSize(args: readonly string[]): number { + const index = args.indexOf("--fft-size"); + if (index < 0) return CLI_CAPTURE_DEFAULT_FFT_SIZE; + + const fftSize = Number(args[index + 1]); + if ( + !Number.isSafeInteger(fftSize) || + fftSize < 256 || + fftSize > 8_388_608 || + (fftSize & (fftSize - 1)) !== 0 + ) { + throw new Error( + "--fft-size must be a power of two from 256 through 8388608", + ); + } + return fftSize; +} + +/** + * Resolves N-APT's receive defaults for a selected source. RTL-SDR uses its + * maximum supported manual tuner gain and the frequency correction required + * by the N-APT signal profile. Other devices retain backend-resolved values. + */ +export function resolveNaptReceiveDefaults(source: { + sdr?: { + settings?: { + gain?: number | { tuner_gain?: number }; + ppm?: number; + }; + }; +}): NaptReceiveDefaults { + const configuredGain = source.sdr?.settings?.gain; + const gainDb = + typeof configuredGain === "number" + ? configuredGain + : configuredGain?.tuner_gain; + + return { + gainDb: gainDb ?? 46.9, + ppm: source.sdr?.settings?.ppm ?? 1, + }; +} + +/** Returns true only when Rust reports the requested manual receive values. */ +export function hasNaptReceiveDefaults( + source: { + sdr?: { + settings?: { + gain?: number | { tuner_gain?: number }; + ppm?: number; + }; + }; + }, + defaults: NaptReceiveDefaults, +): boolean { + const configuredGain = source.sdr?.settings?.gain; + const gainDb = + typeof configuredGain === "number" + ? configuredGain + : configuredGain?.tuner_gain; + return ( + gainDb === defaults.gainDb && source.sdr?.settings?.ppm === defaults.ppm + ); +} + +const isConnected = (source: CaptureSource) => + source.status === "connected" || + source.status === "receiving" || + source.status === "paused" || + source.status === "streaming"; + +/** + * Selects a stable backend source ID for a CLI operation. + * + * `auto` selects the only connected source and rejects ambiguity. Explicit + * IDs are still used for routing, but source identity does not affect policy. + */ +export function resolveRequestedDevice({ + requested, + sources, +}: { + requested: string; + sources: CaptureSource[]; +}): CaptureSource { + const connected = sources.filter(isConnected); + + if (requested !== "auto") { + const selected = sources.find((source) => source.id === requested); + if ( + !selected || + selected.status !== "connected" && + selected.status !== "receiving" && + selected.status !== "paused" && + selected.status !== "streaming" + ) { + throw new Error(`Device '${requested}' is unavailable`); + } + return selected; + } + + if (connected.length > 1) { + throw new Error( + "Multiple sources are available; specify --device", + ); + } + return connected[0] ?? (() => { + throw new Error("No connected source is available"); + })(); +} + +/** + * Resolves either a configured channel or an explicit center frequency. + * Channels and center frequencies are mutually exclusive. A channel range + * is capped at its configured start plus the active sample-rate span. + */ +export function resolveCaptureTarget( + options: { channel?: string; centerFrequencyHz?: number }, + capabilities: CaptureCapabilities, +): CaptureTarget { + if (options.channel && options.centerFrequencyHz !== undefined) { + throw new Error("--channel and --center-frequency are mutually exclusive"); + } + + if (options.channel) { + const bounds = capabilities.channels[options.channel]; + if (!bounds) throw new Error(`Unknown channel '${options.channel}'`); + const maxHz = Math.min(bounds.max, bounds.min + capabilities.sampleRateHz); + return { + channel: options.channel, + minHz: bounds.min, + maxHz, + centerFrequencyHz: bounds.min + (maxHz - bounds.min) / 2, + }; + } + + const center = options.centerFrequencyHz; + if (center === undefined) { + return resolveCaptureTarget({ channel: "A" }, capabilities); + } + + if (!Number.isFinite(center) || center <= 0) { + throw new Error("--center-frequency must be a positive finite frequency"); + } + const halfRate = capabilities.sampleRateHz / 2; + return { + minHz: center - halfRate, + maxHz: center + halfRate, + centerFrequencyHz: center, + }; +} + +/** + * Validates file/encryption combinations before a capture starts. + * `.napt` artifacts are always encrypted and cannot be requested unencrypted. + */ +export function validateIqCaptureOptions(options: { + fileType?: ".napt" | ".wav" | ".iq"; + encrypted?: boolean; +}): void { + if (options.fileType === ".napt" && options.encrypted === false) { + throw new Error(".napt captures are always encrypted"); + } +} diff --git a/src/ts/features/capture/sidebar/IQCaptureControlsSection.tsx b/src/ts/features/capture/sidebar/IQCaptureControlsSection.tsx index 97e5bb1b..36431e7a 100644 --- a/src/ts/features/capture/sidebar/IQCaptureControlsSection.tsx +++ b/src/ts/features/capture/sidebar/IQCaptureControlsSection.tsx @@ -44,6 +44,84 @@ const Section = styled.div` gap: inherit; `; +const CAPTURE_DOWNLOADS_STORAGE_KEY = "napt.iq-capture-downloads.v1"; +const CAPTURE_DOWNLOAD_RETENTION_MS = 48 * 60 * 60 * 1000; + +type PersistedCaptureDownload = { + jobId: string; + downloadUrl: string; + filename?: string; + fileSize?: number; + duration?: number; + timestamp: number; +}; + +const loadPersistedCaptureDownloads = (): PersistedCaptureDownload[] => { + try { + const raw = window.localStorage.getItem(CAPTURE_DOWNLOADS_STORAGE_KEY); + if (!raw) return []; + const now = Date.now(); + const parsed = JSON.parse(raw) as PersistedCaptureDownload[]; + return parsed.filter( + (download) => + typeof download.jobId === "string" && + typeof download.downloadUrl === "string" && + typeof download.timestamp === "number" && + now - download.timestamp < CAPTURE_DOWNLOAD_RETENTION_MS, + ); + } catch { + return []; + } +}; + +const persistCaptureDownloads = (downloads: PersistedCaptureDownload[]) => { + try { + window.localStorage.setItem( + CAPTURE_DOWNLOADS_STORAGE_KEY, + JSON.stringify(downloads), + ); + } catch { + // Storage can be unavailable in private or restricted browser contexts. + } +}; + +const formatRelativeCaptureTime = (timestamp: number, now: number): string => { + const elapsedSeconds = Math.max(0, Math.floor((now - timestamp) / 1000)); + if (elapsedSeconds < 60) return `${elapsedSeconds}s ago`; + const elapsedMinutes = Math.floor(elapsedSeconds / 60); + if (elapsedMinutes < 60) return `${elapsedMinutes}m ago`; + const elapsedHours = Math.floor(elapsedMinutes / 60); + if (elapsedHours < 24) return `${elapsedHours} hrs ago`; + const elapsedDays = Math.floor(elapsedHours / 24); + const remainingHours = elapsedHours % 24; + if (elapsedDays === 1 && remainingHours >= 18) { + return "almost 2 days ago"; + } + if (elapsedDays === 1 && remainingHours >= 12) { + return "1 day and a half ago"; + } + if (remainingHours === 0) { + return `${elapsedDays} ${elapsedDays === 1 ? "day" : "days"} ago`; + } + return `${elapsedDays} ${elapsedDays === 1 ? "day" : "days"}, ${remainingHours} hrs ago`; +}; + +const useCaptureDownloadClock = () => { + const [now, setNow] = React.useState(() => Date.now()); + + React.useEffect(() => { + const updateClock = () => setNow(Date.now()); + const timer = window.setInterval(updateClock, 60_000); + document.addEventListener("visibilitychange", updateClock); + return () => { + window.clearInterval(timer); + document.removeEventListener("visibilitychange", updateClock); + }; + }, []); + + return now; +}; + // Channel descriptor used to trim a multi-channel capture header end-to-end export interface ChannelDescriptor { center_freq_hz: number; @@ -317,6 +395,16 @@ const InfoCardTitle = styled.div` font-family: ${(props) => props.theme.typography.mono}; `; +const CaptureAvailableDot = styled.span` + display: inline-block; + width: 12px; + height: 12px; + margin-left: 7px; + border-radius: 50%; + background: ${(props) => props.theme.primary}; + vertical-align: middle; +`; + const InfoRow = styled.div` display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -338,12 +426,13 @@ const DownloadMeta = styled.div` font-family: ${(props) => props.theme.typography.mono}; `; -const DownloadCard = styled.div` +const DownloadCard = styled.div<{ $latest?: boolean }>` display: grid; gap: 12px; padding: 10px 12px; border-radius: 6px; min-width: 0; + opacity: ${(props) => (props.$latest ? 1 : 0.85)}; `; const DownloadLink = styled.a` @@ -452,6 +541,7 @@ interface IQCaptureControlsSectionProps { captureDurationMode: "timed" | "manual"; captureDurationS: number; captureFileType: CaptureFileType; + isMockSource?: boolean; acquisitionMode: "stepwise" | "interleaved" | "whole_sample"; captureEncrypted: boolean; capturePlayback: boolean; @@ -489,6 +579,7 @@ export const IQCaptureControlsSection: React.FC< captureDurationMode, captureDurationS, captureFileType, + isMockSource = false, acquisitionMode, captureEncrypted, capturePlayback, @@ -513,6 +604,10 @@ export const IQCaptureControlsSection: React.FC< channels, onCaptureWithChannels, }) => { + const captureDownloadNow = useCaptureDownloadClock(); + const [persistedDownloads, setPersistedDownloads] = React.useState< + PersistedCaptureDownload[] + >(loadPersistedCaptureDownloads); // Build derived channels from the capture range segments when channels aren't provided const derivedChannels: ChannelDescriptor[] = React.useMemo(() => { if (!captureRange?.segments) return []; @@ -664,6 +759,10 @@ export const IQCaptureControlsSection: React.FC< // Raw .iq is the lossless default fallback when .napt eligibility is lost. React.useEffect(() => { + if (isMockSource && captureFileType === ".napt") { + onCaptureFileTypeChange(".iq"); + return; + } if ( captureFileType === ".napt" && !naptValidation.isValid && @@ -674,6 +773,7 @@ export const IQCaptureControlsSection: React.FC< }, [ naptValidation.isValid, captureFileType, + isMockSource, onCaptureFileTypeChange, activeCaptureAreas.length, ]); @@ -743,6 +843,34 @@ export const IQCaptureControlsSection: React.FC< } }, [captureStatus, dispatch]); + React.useEffect(() => { + if (captureStatus?.status !== "done" || !captureStatus.downloadUrl) return; + const download: PersistedCaptureDownload = { + jobId: captureStatus.jobId, + downloadUrl: captureStatus.downloadUrl, + filename: captureStatus.filename, + fileSize: captureStatus.fileSize, + duration: captureStatus.duration, + timestamp: captureStatus.timestamp ?? Date.now(), + }; + setPersistedDownloads((current) => { + const next = [ + download, + ...current.filter((item) => item.jobId !== download.jobId), + ].filter( + (item) => Date.now() - item.timestamp < CAPTURE_DOWNLOAD_RETENTION_MS, + ); + persistCaptureDownloads(next); + return next; + }); + }, [captureStatus]); + + const clearPersistedDownloads = () => { + setPersistedDownloads([]); + persistCaptureDownloads([]); + onClearStatus(); + }; + // Calculate capture range span to determine appropriate mode const captureRangeSpan = captureRange.max - captureRange.min; const hardwareSampleRateHz = maxSampleRate; @@ -805,7 +933,7 @@ export const IQCaptureControlsSection: React.FC< (!isCaptureActive && !hasSelectedCaptureAreas); const handleGeolocationToggle = async (enabled: boolean) => { - if (enabled && captureFileType === ".napt") { + if (enabled) { const hasPermission = await requestPermission(); if (!hasPermission) { // If permission denied, keep toggle off @@ -886,7 +1014,7 @@ export const IQCaptureControlsSection: React.FC< onCaptureFileTypeChange(e.target.value as CaptureFileType) } > - @@ -936,40 +1064,43 @@ export const IQCaptureControlsSection: React.FC< } > - + onCaptureEncryptedChange(e.target.checked)} /> - + } tooltipTitle="Location data (lat, long, accuracy, altitude)" - tooltip="Only available for .napt files. Requires browser permission to access location." + tooltip="Adds your current location to the capture metadata. Requires browser permission to access location." > handleGeolocationToggle(e.target.checked)} /> - {geoError && captureFileType === ".napt" && ( + {geoError && ( {geoError} @@ -999,52 +1130,45 @@ export const IQCaptureControlsSection: React.FC< Downloads Clear - {captureStatus?.downloadUrl && isAuthenticated ? ( - - -
- - {captureStatus.filename || "Download"} - - - {typeof captureStatus.fileSize === "number" && - formatFileSize(captureStatus.fileSize)} - {" / "} - {typeof captureStatus.duration === "number" && - formatDurationMs(captureStatus.duration)} - -
- - {captureStatus?.status === "done" - ? "Complete" - : captureStatus?.status === "failed" - ? "Failed" - : "In progress..."} - -
-
+ {persistedDownloads.length > 0 && isAuthenticated ? ( + persistedDownloads.map((download) => ( + + +
+ + {download.filename || "Download"} + + + {formatRelativeCaptureTime( + download.timestamp, + captureDownloadNow, + )} + {" / "} + {typeof download.fileSize === "number" && + formatFileSize(download.fileSize)} + {" / "} + {typeof download.duration === "number" && + formatDurationMs(download.duration)} + +
+ Complete +
+
+ )) ) : ( @@ -1066,7 +1190,14 @@ export const IQCaptureControlsSection: React.FC< {variant === "sidebar" ? ( } - label="Take an I/Q Capture" + label={ + <> + Take an I/Q Capture + {persistedDownloads.length > 0 && ( + + )} + + } defaultOpen={defaultOpen} open={open} sectionId="iq-capture" diff --git a/src/ts/features/demodulation/utils/demodHarness.ts b/src/ts/features/demodulation/utils/demodHarness.ts new file mode 100644 index 00000000..527502c0 --- /dev/null +++ b/src/ts/features/demodulation/utils/demodHarness.ts @@ -0,0 +1,60 @@ +import { DEMOD_MIN_FFT_SIZE } from "@n-apt/demodulation/utils/demodQuality"; +import { + createDemodProcessor, + type DemodAlgorithm, +} from "@n-apt/demodulation/utils/demodProcessors"; + +export type { DemodAlgorithm }; +export type DemodHarnessInput = { + centerFrequencyHz: number; + frequencyRangeHz: [number, number]; + sampleRateHz: number; + algorithm: DemodAlgorithm; + targetFps?: number; + fftSize?: number; +}; +export type DemodPlan = DemodHarnessInput & { + fftSize: number; + temporalResolution: "lossless"; + bandwidthHz: number; + trailer: { processing: { operation: "demodulate"; algorithm: DemodAlgorithm; parameters: Record } }; +}; + +function nextPowerOfTwo(value: number): number { + let n = 1; + while (n < value) n *= 2; + return n; +} + +export function prepareDemodulation(input: DemodHarnessInput): DemodPlan { + if (!Number.isFinite(input.centerFrequencyHz) || input.frequencyRangeHz[1] <= input.frequencyRangeHz[0]) throw new Error("invalid demod frequency area"); + const targetFps = input.targetFps ?? 60; + const fftSize = Math.max(DEMOD_MIN_FFT_SIZE, nextPowerOfTwo(input.fftSize ?? input.sampleRateHz / targetFps)); + return { + ...input, + fftSize, + temporalResolution: "lossless", + bandwidthHz: input.frequencyRangeHz[1] - input.frequencyRangeHz[0], + trailer: { processing: { operation: "demodulate", algorithm: input.algorithm, parameters: { targetFps } } }, + }; +} + +export function runDemodulationAlgorithm( + algorithm: string, + iq: Uint8Array, + options: { sampleRateHz?: number; targetSampleRate?: number; centerFrequencyHz?: number; bandwidthHz?: number } = {}, +): Float32Array { + if ( + algorithm !== "fm" && + algorithm !== "fmDiscriminator" && + algorithm !== "aptAudio" && + algorithm !== "aptImage" + ) { + throw new Error(`Unsupported demodulation algorithm: ${algorithm}`); + } + return createDemodProcessor(algorithm, { + targetSampleRate: options.targetSampleRate ?? 48_000, + centerFrequency: options.centerFrequencyHz, + bandwidth: options.bandwidthHz, + }).process(iq, options.sampleRateHz ?? 2_400_000, options.centerFrequencyHz); +} diff --git a/src/ts/features/settings/hooks/useSdrSettings.ts b/src/ts/features/settings/hooks/useSdrSettings.ts index 710b9fc5..8078da9b 100644 --- a/src/ts/features/settings/hooks/useSdrSettings.ts +++ b/src/ts/features/settings/hooks/useSdrSettings.ts @@ -281,9 +281,7 @@ export const useSdrSettings = ({ const basebandIsPinned = stateRef.current.basebandFilterPinned; const nextHackrfBasebandBandwidth = basebandIsPinned ? stateRef.current.hackrfBasebandBandwidth - : stateRef.current.hackrfBasebandBandwidth === 0 - ? 0 - : sampleRate; + : sampleRate; dispatch( setSdrSettingsBundle({ sampleRateHz: sampleRate, @@ -296,8 +294,10 @@ export const useSdrSettings = ({ sendCurrentSettings({ sampleRate, frameRate: nextFrameRate, - ...(nextHackrfBasebandBandwidth !== null && - nextHackrfBasebandBandwidth !== undefined + // A disabled filter (0) is held in state but never published: zero is + // not a valid MAX2837 width and would fail the hardware filter set. + ...(typeof nextHackrfBasebandBandwidth === "number" && + nextHackrfBasebandBandwidth > 0 ? { tunerBandwidth: nextHackrfBasebandBandwidth } : {}), }); @@ -343,10 +343,19 @@ export const useSdrSettings = ({ ); const setHackrfBasebandBandwidth = useCallback( (hackrfBasebandBandwidth: number | null) => { - dispatch(setSdrSettingsBundle({ hackrfBasebandBandwidth })); - sendCurrentSettings({ - tunerBandwidth: hackrfBasebandBandwidth ?? undefined, - }); + // Zero is the "filter off" sentinel: it is kept in state so the control + // can render the disabled filter, but it is never published to the + // device. A zero width is not a valid MAX2837 filter — writing it fails + // the hardware filter set and tears the live stream down. + const nextBandwidth = + typeof hackrfBasebandBandwidth === "number" && + Number.isFinite(hackrfBasebandBandwidth) + ? Math.max(0, Math.round(hackrfBasebandBandwidth)) + : null; + + dispatch(setSdrSettingsBundle({ hackrfBasebandBandwidth: nextBandwidth })); + if (nextBandwidth === null || nextBandwidth === 0) return; + sendCurrentSettings({ tunerBandwidth: nextBandwidth }); }, [dispatch, sendCurrentSettings], ); diff --git a/src/ts/features/spectrum/FFTCanvas.tsx b/src/ts/features/spectrum/FFTCanvas.tsx index 55b09c18..3aba003d 100644 --- a/src/ts/features/spectrum/FFTCanvas.tsx +++ b/src/ts/features/spectrum/FFTCanvas.tsx @@ -3665,7 +3665,18 @@ const FFTCanvas = memo( if (!hasNewData && !shouldReprocessCurrentFrame && !isStandby) { if (isPaused) { - if (!recoverPausedWaveformRef.current()) { + // A retained paused frame must keep painting. Overlay chrome that + // is invalidated after the last pass — the grid, axes and labels + // live in an overlay texture that is only re-uploaded from a draw + // — would otherwise never come back and the canvas would stay + // stranded with a stale presentation. Snapshot recovery only + // supplies a waveform when the retained frame has none, and file + // playback disables it, so a cached waveform is paintable on its + // own. + const hasCachedWaveform = + !!renderWaveformRef.current && + renderWaveformRef.current.length > 0; + if (!hasCachedWaveform && !recoverPausedWaveformRef.current()) { return; } } else if (!mirrorPanOnlyRedraw && !viewportOnlyRedraw) { diff --git a/src/ts/features/spectrum/FFTPlaybackCanvas.tsx b/src/ts/features/spectrum/FFTPlaybackCanvas.tsx index 4600540f..492e1d2b 100644 --- a/src/ts/features/spectrum/FFTPlaybackCanvas.tsx +++ b/src/ts/features/spectrum/FFTPlaybackCanvas.tsx @@ -288,6 +288,7 @@ const FFTPlaybackCanvas = forwardRef( setChannelCount, setActiveChannel, setFrequencyRange, + playIntegrityFailedFile, } = useStitchingLogic({ selectedFiles, stitchTrigger, @@ -764,7 +765,17 @@ const FFTPlaybackCanvas = forwardRef( ? "No files selected" : `${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""} selected`} - {stitchStatus.toLowerCase().includes("decryption") ? ( + {stitchStatus.toLowerCase().includes("integrity failed") ? ( +
+ + This file appears corrupted or modified. Its contents do not + match the integrity record. + + +
+ ) : stitchStatus.toLowerCase().includes("decryption") ? (
{ - const sampleRateModeRef = useRef(null); + // The selected mode and the pending local rate are rendered by this hook, so + // they are state rather than refs. A ref mutation does not re-render, which let + // effects run against a stale mode/rate until an unrelated render refreshed + // them ("stale until you keep interacting"). Both change only on explicit + // user actions, never per frame, so the extra renders are gesture-frequency. + const [sampleRateMode, setSampleRateMode] = useState( + null, + ); const lastAppliedWholeChannelRateRef = useRef(null); const lastAppliedFrequencyRangeKeyRef = useRef(null); - const lastObservedFrequencyRangeKeyRef = useRef(null); - const lastExplicitChannelFocusRangeKeyRef = useRef(null); - const pendingSampleRateRef = useRef(null); - const requestedSampleRateHz = pendingSampleRateRef.current ?? sampleRateHz; + const [pendingSampleRateHz, setPendingSampleRateHz] = useState( + null, + ); + const requestedSampleRateHz = pendingSampleRateHz ?? sampleRateHz; useEffect(() => { if ( - pendingSampleRateRef.current !== null && + pendingSampleRateHz !== null && typeof sampleRateHz === "number" && - Math.round(sampleRateHz) === Math.round(pendingSampleRateRef.current) + Math.round(sampleRateHz) === Math.round(pendingSampleRateHz) ) { - pendingSampleRateRef.current = null; + setPendingSampleRateHz(null); } - }, [sampleRateHz]); + }, [pendingSampleRateHz, sampleRateHz]); const canUseWholeChannel = sourceMode === "live" && @@ -265,8 +272,8 @@ export const useLiveSampleRateControl = ({ const isWholeChannelMode = canUseWholeChannel && - (sampleRateModeRef.current === "whole" || - (sampleRateModeRef.current !== "manual" && + (sampleRateMode === "whole" || + (sampleRateMode !== "manual" && resolveWholeChannelMode({ supportsWholeChannel: true, sampleRateHz: requestedSampleRateHz, @@ -317,24 +324,17 @@ export const useLiveSampleRateControl = ({ Math.round(nextWholeChannelRate) === Math.round(nextSampleRate) ? nextWholeChannelRate : nextSampleRate; - pendingSampleRateRef.current = resolvedSampleRate; - if (requestedMode === "whole" && frequencyRangeOverride) { - const normalizedOverride = normalizeFrequencyRangeToHz( - frequencyRangeOverride, - ); - lastExplicitChannelFocusRangeKeyRef.current = - `${normalizedOverride.min}:${normalizedOverride.max}`; - } + setPendingSampleRateHz(resolvedSampleRate); if (requestedMode) { - sampleRateModeRef.current = requestedMode; + setSampleRateMode(requestedMode); } else if ( wholeChannelSampleRate && Math.round(wholeChannelSampleRate) !== Math.round(resolvedSampleRate) ) { - sampleRateModeRef.current = "manual"; + setSampleRateMode("manual"); } else { - sampleRateModeRef.current = "whole"; + setSampleRateMode("whole"); } let nextRange: FrequencyRange | null = null; @@ -417,7 +417,7 @@ export const useLiveSampleRateControl = ({ if (currentRate !== null && currentRate > 0) { if ( preferWholeChannelOnLiveStart && - sampleRateModeRef.current === null && + sampleRateMode === null && currentRate === 3_200_000 && currentRate !== nextRate ) { @@ -438,7 +438,7 @@ export const useLiveSampleRateControl = ({ if (!Number.isFinite(nextRate) || nextRate <= 0) return; - if (sampleRateModeRef.current === "manual") { + if (sampleRateMode === "manual") { return; } if (lastAppliedWholeChannelRateRef.current === nextRate) return; @@ -467,6 +467,7 @@ export const useLiveSampleRateControl = ({ requestedSampleRateHz, setSampleRate, onSampleRateApplied, + sampleRateMode, sourceMode, startingAnchorPosition, wholeChannelSampleRate, @@ -485,56 +486,31 @@ export const useLiveSampleRateControl = ({ } const currentSpan = rangeSpanHz(frequencyRange); - const normalizedCurrentRange = - normalizeFrequencyRangeToHz(frequencyRange); - const currentRangeKey = `${normalizedCurrentRange.min}:${normalizedCurrentRange.max}`; - const rangeChangedSinceObservation = - lastObservedFrequencyRangeKeyRef.current !== null && - lastObservedFrequencyRangeKeyRef.current !== currentRangeKey; - lastObservedFrequencyRangeKeyRef.current = currentRangeKey; if (canUseWholeChannel) { const targetRate = isWholeChannelMode && typeof wholeChannelSampleRate === "number" ? wholeChannelSampleRate : requestedSampleRateHz; + // Span-only reconciliation, anchored on the current centre. The channel + // bounds deliberately do NOT participate: passing them made the builder + // clamp the window back inside the active channel (and re-publish whenever + // that clamp moved the window), which fought every scroll past the channel + // and could oscillate. Position belongs to explicit channel/sample-rate + // actions; this effect only keeps the acquisition width equal to the rate. const nextRange = buildLiveSampleRateRange({ currentRange: frequencyRange, sampleRateHz: targetRate, - channelBounds: activeSignalAreaBounds, - startingAnchorPosition: isWholeChannelMode - ? startingAnchorPosition - : "center", + startingAnchorPosition: "center", }); - const wholeChannelEdgesDiffer = - normalizedCurrentRange.min !== nextRange.min || - normalizedCurrentRange.max !== nextRange.max; - const nextRangeKey = `${nextRange.min}:${nextRange.max}`; - const isExplicitChannelFocus = - lastExplicitChannelFocusRangeKeyRef.current === nextRangeKey; - if ( - isExplicitChannelFocus && - rangeChangedSinceObservation && - lastAppliedFrequencyRangeKeyRef.current !== currentRangeKey - ) { - // A changed live range is a new observation. If it does not equal the - // range we last requested, allow Whole Channel edge reconciliation to - // publish again rather than treating the old request as an ACK. - lastAppliedFrequencyRangeKeyRef.current = null; - } - if ( - (isWholeChannelMode && isExplicitChannelFocus) - ? wholeChannelEdgesDiffer - : rangeSpanHz(frequencyRange) !== rangeSpanHz(nextRange) - ) { + if (currentSpan !== rangeSpanHz(nextRange)) { applyFrequencyRangeIfChanged(nextRange); } } else if (currentSpan > requestedSampleRateHz) { const nextRange = buildLiveSampleRateRange({ currentRange: frequencyRange, sampleRateHz: requestedSampleRateHz, - channelBounds: activeSignalAreaBounds, startingAnchorPosition: "center", }); @@ -547,8 +523,13 @@ export const useLiveSampleRateControl = ({ applyFrequencyRangeIfChanged, canUseWholeChannel, frequencyRange, + // Both are read below and both are derived from the selected mode and the + // channel span — not from the range. Leaving them out let this effect run + // with a stale whole-channel snapshot until an unrelated render refreshed it. + isWholeChannelMode, requestedSampleRateHz, startingAnchorPosition, + wholeChannelSampleRate, ]); return { diff --git a/src/ts/features/spectrum/hooks/useSpectrumInteraction.ts b/src/ts/features/spectrum/hooks/useSpectrumInteraction.ts index 59f0261c..426f9019 100644 --- a/src/ts/features/spectrum/hooks/useSpectrumInteraction.ts +++ b/src/ts/features/spectrum/hooks/useSpectrumInteraction.ts @@ -1067,6 +1067,12 @@ export function useSpectrumInteraction({ return { min: minPan, max: maxPan }; }; + // Navigation/VFO pan is bounded by the device spectrum, never by the active + // channel. Falling back to channel bounds here trapped panning inside the + // selected channel before the hardware bounds hydrated. + const navigationPanBounds = (): FrequencyRange => + hardwareSpectrumBounds ?? getAvailableSpectrumBounds(null); + const clampVizPan = ( pan: number, sourceRange: FrequencyRange, @@ -1207,7 +1213,7 @@ export function useSpectrumInteraction({ nextPan - (newHardwareCenter - currentHardwareCenter), clampedHardwareRange, zoom, - hardwareSpectrumBounds ?? signalAreaBounds?.[activeSignalArea], + navigationPanBounds(), ); onVizPanReanchor?.(remainingPan); if (!onVizPanReanchor) onVizPanChange(remainingPan); @@ -1730,8 +1736,7 @@ export function useSpectrumInteraction({ newPan, bounds, zoom, - hardwareSpectrumBounds ?? - signalAreaBounds?.[activeSignalArea], + navigationPanBounds(), ); onVizPanChange(clampedPan); if (vizPanOffsetRef) { @@ -1813,7 +1818,7 @@ export function useSpectrumInteraction({ desiredPan, frequencyRangeRef.current, zoom, - hardwareSpectrumBounds ?? signalAreaBounds?.[activeSignalArea], + navigationPanBounds(), ); onVizPanChange(clampedPan); if (vizPanOffsetRef) { @@ -2865,7 +2870,7 @@ export function useSpectrumInteraction({ newPan, frequencyRangeRef.current, zoom, - hardwareSpectrumBounds ?? signalAreaBounds?.[activeSignalArea], + navigationPanBounds(), ); onVizPanChange(newPan); vizPanOffsetRef.current = newPan; diff --git a/src/ts/features/spectrum/hooks/useSpectrumStore.tsx b/src/ts/features/spectrum/hooks/useSpectrumStore.tsx index 698c014c..ee3e9a99 100644 --- a/src/ts/features/spectrum/hooks/useSpectrumStore.tsx +++ b/src/ts/features/spectrum/hooks/useSpectrumStore.tsx @@ -115,6 +115,7 @@ import { } from "@n-apt/app/infrastructure/visualization/fftVisualizerMachine"; import { clampFrequencyRangeToBounds, + getAvailableSpectrumBounds, normalizeFrequencyRangeToHz, } from "@n-apt/math/frequency"; import { @@ -1094,17 +1095,17 @@ const PERSISTED_SOURCE_VIEW_FIELDS: Array = [ ]; /** - * Live acquisition bounds are device-scoped. A channel frame is only a - * hydration fallback; letting it win at 1x makes a subscriber's zoom history - * decide whether the shared source can reach an edge such as 0 Hz. + * Live acquisition bounds are device-scoped, never subscriber-local. The active + * channel must not constrain acquisition: clamping to a channel frame trapped + * the acquisition window inside the channel whenever the device bounds had not + * hydrated, so every scroll snapped back. Use the device bounds when known, + * otherwise the global spectrum bounds the backend advertises. */ export const resolveLiveAcquisitionBounds = ({ hardwareBounds, - channelBounds, }: { hardwareBounds?: FrequencyRange | null; - channelBounds?: FrequencyRange | null; -}): FrequencyRange | null => { +}): FrequencyRange => { if ( hardwareBounds && Number.isFinite(hardwareBounds.min) && @@ -1113,7 +1114,12 @@ export const resolveLiveAcquisitionBounds = ({ ) { return hardwareBounds; } - return channelBounds ?? null; + // The active channel must never bound live acquisition. Falling back to a + // subscriber-local channel frame trapped the acquisition window inside the + // channel whenever the device bounds had not hydrated, so every scroll + // snapped back to the channel. Fall back to the global spectrum bounds the + // backend advertises, exactly like navigation bounds do. + return getAvailableSpectrumBounds(null); }; // Every persisted source-view field is subscriber-local and safe to restore @@ -3805,11 +3811,6 @@ const SpectrumProviderReal: React.FC<{ children: React.ReactNode }> = memo( return bounds; }, [effectiveFrames]); - const activeSignalAreaBounds = - signalAreaBounds?.[mergedState.activeSignalArea] ?? - signalAreaBounds?.[mergedState.activeSignalArea?.toLowerCase?.()] ?? - null; - const clampLiveFrequencyRange = useCallback( (range: FrequencyRange) => { if (range.min < 0) { @@ -3819,9 +3820,7 @@ const SpectrumProviderReal: React.FC<{ children: React.ReactNode }> = memo( } const bounds = resolveLiveAcquisitionBounds({ hardwareBounds: hardwareSpectrumBounds, - channelBounds: activeSignalAreaBounds, }); - if (!bounds) return normalizeFrequencyRangeToHz(range); const rangeSpan = range.max - range.min; const boundsSpan = bounds.max - bounds.min; @@ -3854,7 +3853,6 @@ const SpectrumProviderReal: React.FC<{ children: React.ReactNode }> = memo( ); }, [ - activeSignalAreaBounds, backend, deviceName, deviceProfile?.is_rtl_sdr, diff --git a/src/ts/features/spectrum/hooks/useStitchingLogic.ts b/src/ts/features/spectrum/hooks/useStitchingLogic.ts index 267650ba..dee3378c 100644 --- a/src/ts/features/spectrum/hooks/useStitchingLogic.ts +++ b/src/ts/features/spectrum/hooks/useStitchingLogic.ts @@ -36,7 +36,8 @@ interface StitchingResult { setActiveChannel: (channel: number) => void; setFrequencyRange: (range: { min: number; max: number }) => void; setHardwareSampleRateHz: (hz: number | undefined) => void; - stitchFiles: () => Promise; + stitchFiles: (allowIntegrityFailure?: boolean) => Promise; + playIntegrityFailedFile: () => Promise; } export const useStitchingLogic = ({ @@ -176,7 +177,7 @@ export const useStitchingLogic = ({ onProcessedDataChange?.(true); } - const stitchFiles = useCallback(async () => { + const stitchFiles = useCallback(async (allowIntegrityFailure = false) => { const currentFiles = selectedFilesRef.current; if (currentFiles.length === 0) { setStitchStatus("No files selected for stitching"); @@ -217,6 +218,7 @@ export const useStitchingLogic = ({ }, aesKeyRef.current, sampleRateOptions, // Pass dynamic sample rate options + allowIntegrityFailure, ); if (!result.stitchedData) { @@ -307,7 +309,9 @@ export const useStitchingLogic = ({ console.error("Stitch error:", error); const msg = error.message || String(error); setStitchStatus( - msg.toLowerCase().includes("decryption") + msg.toLowerCase().includes("integrity_failed") + ? "File integrity failed: file appears corrupted or modified" + : msg.toLowerCase().includes("decryption") ? "File decryption failed, wrong key" : msg, ); @@ -327,6 +331,11 @@ export const useStitchingLogic = ({ onProcessedDataChange, ]); + const playIntegrityFailedFile = useCallback( + () => stitchFiles(true), + [stitchFiles], + ); + // Trigger: respond to parent's stitch button click useEffect(() => { if (lastTriggerRef.current === null) { @@ -367,5 +376,6 @@ export const useStitchingLogic = ({ setFrequencyRange, setHardwareSampleRateHz, stitchFiles, + playIntegrityFailedFile, }; }; diff --git a/src/ts/features/spectrum/hooks/useWebGPUInit.ts b/src/ts/features/spectrum/hooks/useWebGPUInit.ts index 0661b832..8c993520 100644 --- a/src/ts/features/spectrum/hooks/useWebGPUInit.ts +++ b/src/ts/features/spectrum/hooks/useWebGPUInit.ts @@ -269,6 +269,31 @@ export function useWebGPULifecycle({ const overlayDirtyRef = useRef({ grid: true, markers: true, spikes: true }); const overlayLastUploadMsRef = useRef({ grid: 0, markers: 0, spikes: 0 }); + /** + * The overlay textures (grid, markers, spikes) are composed in the same pass + * as the spectrum itself. They must exist before the canvas is allowed to + * paint: creating them from an effect leaves the first painted frame without + * a grid, and the axis/label chrome pops in a frame later. A rebuilt renderer + * starts with no texture, so the dirty flags are re-armed with it. + */ + const buildOverlayRenderers = useCallback( + (device: GPUDevice, format: GPUTextureFormat) => { + const rendererRefs = [ + gridOverlayRendererRef, + markersOverlayRendererRef, + spikesOverlayRendererRef, + ]; + for (const rendererRef of rendererRefs) { + rendererRef.current?.destroy(); + rendererRef.current = new OverlayTextureRenderer(device, format); + } + overlayDirtyRef.current.grid = true; + overlayDirtyRef.current.markers = true; + overlayDirtyRef.current.spikes = true; + }, + [], + ); + const initializeResamplePipeline = useCallback( async (device: GPUDevice) => { try { @@ -351,10 +376,13 @@ export function useWebGPULifecycle({ } webgpuDeviceRef.current = device; - webgpuFormatRef.current = getPreferredCanvasFormat(); + const format = getPreferredCanvasFormat(); + webgpuFormatRef.current = format; webgpuContextLostRef.current = false; webgpuRetryCountRef.current = 0; + buildOverlayRenderers(device, format); + device.onuncapturederror = () => { webgpuContextLostRef.current = true; setWebgpuEnabled(false); @@ -415,41 +443,7 @@ export function useWebGPULifecycle({ cancelled = true; clearTimeout(retryTimerId); }; - }, []); - - // Create overlay renderers once device/format are ready - useEffect(() => { - if (!webgpuEnabled || webgpuContextLostRef.current) return; - const device = webgpuDeviceRef.current; - const format = webgpuFormatRef.current; - if (!device || !format) return; - - // Reset all overlay renderers to force fresh initialization - if (!webgpuContextLostRef.current) { - overlayDirtyRef.current.grid = true; - overlayDirtyRef.current.markers = true; - overlayDirtyRef.current.spikes = true; - } - - if (!gridOverlayRendererRef.current) { - gridOverlayRendererRef.current = new OverlayTextureRenderer( - device, - format, - ); - } - if (!markersOverlayRendererRef.current) { - markersOverlayRendererRef.current = new OverlayTextureRenderer( - device, - format, - ); - } - if (!spikesOverlayRendererRef.current) { - spikesOverlayRendererRef.current = new OverlayTextureRenderer( - device, - format, - ); - } - }, [webgpuEnabled, webgpuDeviceRef.current, webgpuFormatRef.current]); + }, [buildOverlayRenderers]); return { isInitialized, diff --git a/src/ts/features/spectrum/sidebar/SourceSettingsSection.tsx b/src/ts/features/spectrum/sidebar/SourceSettingsSection.tsx index 3b15563a..49b94a3d 100644 --- a/src/ts/features/spectrum/sidebar/SourceSettingsSection.tsx +++ b/src/ts/features/spectrum/sidebar/SourceSettingsSection.tsx @@ -344,6 +344,21 @@ export const SourceSettingsSection: React.FC = ({ (deviceType === "rtl-sdr" || deviceType === "rtl_sdr"); const basebandBandwidthVal = hackrfBasebandBandwidth ?? 0; const isHackrfBasebandEnabled = basebandBandwidthVal > 0; + const autoBasebandBandwidth = Math.max( + 0, + Math.round(hackrfCurrentSampleRate || 0), + ); + // While the filter is enabled and not pinned, the sample rate is the value — + // derive it instead of trusting the published prop. Publishing the same rate + // again does not change the prop, so the field would otherwise keep whatever + // the user last typed (e.g. a cleared "0"). + const displayedBasebandBandwidth = + isHackrfBasebandEnabled && !basebandFilterPinned + ? autoBasebandBandwidth + : basebandBandwidthVal; + // Remount the input on blur so an in-progress edit that resolves back to the + // automatic value (cleared to zero) repopulates instead of sticking at 0. + const [basebandInputRevision, setBasebandInputRevision] = React.useState(0); const showBasebandWarning = isHackrfLive && isHackrfBasebandEnabled && @@ -471,18 +486,24 @@ export const SourceSettingsSection: React.FC = ({ onHackrfAmpEnabledChange?.(enabled); }; - const _handleHackrfBasebandBandwidthChange = (raw: string) => { - const val = raw === "" ? 0 : Number(raw); - onHackrfBasebandBandwidthChange?.( - Math.max(0, Number.isFinite(val) ? Math.round(val) : 0), - ); - }; - const handleHackrfBasebandToggle = (enabled: boolean) => { if (!onHackrfBasebandBandwidthChange) return; - onHackrfBasebandBandwidthChange( - enabled ? Math.max(0, Math.round(hackrfCurrentSampleRate || 0)) : 0, - ); + + if (enabled) { + // Switching it back on resumes automatic tracking from the current rate. + onBasebandFilterPinnedChange?.(false); + onHackrfBasebandBandwidthChange( + Math.max(0, Math.round(hackrfCurrentSampleRate || 0)), + ); + return; + } + + // Switching it off must HOLD the disabled state: pin it, otherwise the + // auto-tracking effect sees `0 !== sampleRate` and immediately switches the + // filter back on, which made the toggle look dead. Zero is only the UI + // sentinel here — the settings hook never publishes it to the device. + onBasebandFilterPinnedChange?.(true); + onHackrfBasebandBandwidthChange(0); }; const handleTunerAGCChange = (enabled: boolean) => { @@ -688,13 +709,31 @@ export const SourceSettingsSection: React.FC = ({ {isHackrfBasebandEnabled && ( { - // Typing a custom value pins the filter; clearing it to 0 - // resumes automatic tracking (the toggle below re-enables it). - onBasebandFilterPinnedChange?.(val !== 0); + // Typing a custom value pins the filter. Clearing the field to + // zero resumes automatic tracking: unpinning lets the derived + // sample-rate value take over (and repopulate on blur). Zero is + // never published — it is not a valid MAX2837 width and asking + // the hardware for it tears the stream down. + if (val === 0) { + onBasebandFilterPinnedChange?.(false); + return; + } + onBasebandFilterPinnedChange?.(true); onHackrfBasebandBandwidthChange?.(val); }} + onBlur={(draftHz) => { + // An emptied — or zeroed — field means "resume automatic + // tracking". An empty field emits no change event at all, so + // the pin has to be released here; the remount below then + // shows the current sample rate instead of the previous value. + if (draftHz === null || draftHz === 0) { + onBasebandFilterPinnedChange?.(false); + } + setBasebandInputRevision((n) => n + 1); + }} disabled={!isConnected} minHz={0} maxHz={20000000} diff --git a/src/ts/features/spectrum/sidebar/SpectrumSidebar.tsx b/src/ts/features/spectrum/sidebar/SpectrumSidebar.tsx index 56bc9ce0..9aa8e53b 100644 --- a/src/ts/features/spectrum/sidebar/SpectrumSidebar.tsx +++ b/src/ts/features/spectrum/sidebar/SpectrumSidebar.tsx @@ -584,6 +584,13 @@ export const resolveSidebarSourcePausedState = ({ }): boolean => sourceId === selectedSourceId ? clientPaused : (backendPaused ?? false); +// Whether the current file-mode session was opened by the `?source=fileSelection` +// deep link. This lives at module scope (like the sidebar scroll flags) because +// the sidebar unmounts and remounts as the shell switches routes; a `useRef` +// would be re-derived from the URL and a manual File Selection would then be +// mistaken for a deep-link exit on the next remount. +let fileSelectionDeepLinkOwnsFileMode = false; + export const SpectrumSidebar: React.FC = ({ onCreateNoteCard, visualizerLoading = false, @@ -623,8 +630,8 @@ export const SpectrumSidebar: React.FC = ({ deviceName: liveDeviceName, deviceProfile: liveDeviceProfile, } = useSpectrumStore(); + const isMockSource = selectedSource?.is_mock === true; const spectrumTransport = useSpectrumTransport(); - const lastTxToggleTimeRef = useRef(0); const pendingTxStopSourceIdRef = useRef(null); const lastTxSettingsSyncKeyRef = useRef(null); const txSettingsSyncTimerRef = useRef | null>( @@ -1604,11 +1611,6 @@ export const SpectrumSidebar: React.FC = ({ if (!isConnected) { return; } - const now = Date.now(); - if (nextEnabled && now - lastTxToggleTimeRef.current < 800) { - console.warn("Throttling rapid transmit mode toggle request"); - return; - } const source = selectedSource?.id === sourceId @@ -1748,7 +1750,6 @@ export const SpectrumSidebar: React.FC = ({ }; if (nextEnabled) { - lastTxToggleTimeRef.current = now; if (hasAcceptedTransmitWarning()) { applyToggle(); return; @@ -1767,7 +1768,6 @@ export const SpectrumSidebar: React.FC = ({ return; } - lastTxToggleTimeRef.current = 0; applyToggle(); }, [ @@ -2299,11 +2299,12 @@ export const SpectrumSidebar: React.FC = ({ const handleSourceModeChange = useCallback( (mode: "live" | "file") => { - if (mode === "file") { - setLivePreviewStage(0); - } else { - setLivePreviewStage(0); + if (mode === "live") { + // Leaving file mode releases the deep link's ownership, so a later + // remount does not treat a manual File Selection as a deep-link exit. + fileSelectionDeepLinkOwnsFileMode = false; } + setLivePreviewStage(0); dispatch(setSourceMode(mode)); }, [dispatch], @@ -2326,28 +2327,30 @@ export const SpectrumSidebar: React.FC = ({ handleSourceModeChange("file"); }, [fileSelectionRequested, handleSourceModeChange, sourceMode]); - // Tracks whether the file-selection deep link drove source mode within this - // mount. Leaving the deep link (param removed) resets back to live sources, - // but a manual File Selection on the regular app never sets this, so it is - // not reset. - const fileDeepLinkActiveRef = useRef(fileSelectionSourceRequested); + // Claims the `?source=fileSelection` deep link once per activation so a user + // switch back to a live source is not immediately overridden. + const fileDeepLinkClaimedForParamRef = useRef(false); useEffect(() => { if (fileSelectionSourceRequested) { - fileDeepLinkActiveRef.current = true; - if (sourceMode !== "file") handleSourceModeChange("file"); + if (!fileDeepLinkClaimedForParamRef.current) { + fileDeepLinkClaimedForParamRef.current = true; + fileSelectionDeepLinkOwnsFileMode = true; + if (sourceMode !== "file") handleSourceModeChange("file"); + } return; } + + fileDeepLinkClaimedForParamRef.current = false; // Leaving the file-selection deep link (e.g. navigating back to the start // page) should return the source to live SDR/mock sources instead of // leaving the app stuck in file mode. Skip when the sidebarSection=file // deep link is still requesting file mode, so the two don't fight. if ( - fileDeepLinkActiveRef.current && + fileSelectionDeepLinkOwnsFileMode && !fileSelectionRequested && sourceMode === "file" ) { - fileDeepLinkActiveRef.current = false; handleSourceModeChange("live"); } }, [ @@ -2358,12 +2361,13 @@ export const SpectrumSidebar: React.FC = ({ ]); // Returning to the spectrum view (a fresh mount) after navigating back from - // the file-selection deep link should return to live SDR/mock sources. The - // transition effect above only survives within a mount, so this covers the - // unmount/remount path. It runs once per mount, so a manual File Selection - // made later on the regular app is never reset. + // the file-selection deep link should return to live SDR/mock sources. Only a + // deep-link-owned file session is reset: a manual File Selection leaves the + // ownership flag clear, so a remount keeps the files the user picked instead + // of falling back to the first source. useEffect(() => { if ( + fileSelectionDeepLinkOwnsFileMode && !fileSelectionSourceRequested && !fileSelectionRequested && sourceMode === "file" @@ -2583,7 +2587,7 @@ export const SpectrumSidebar: React.FC = ({ } let geolocationData = undefined; - if (captureFileTypeState === ".napt" && captureGeolocation) { + if (captureGeolocation) { try { const location = await getLocation(); geolocationData = location || undefined; @@ -2616,7 +2620,9 @@ export const SpectrumSidebar: React.FC = ({ durationS: Math.max(1, Math.round(captureDurationS)), fileType: captureFileTypeState, acquisitionMode: effectiveAcquisitionMode, - encrypted: captureFileTypeState === ".napt" ? true : captureEncrypted, + encrypted: + !isMockSource && + (captureFileTypeState === ".napt" || captureEncrypted), fftSize, fftWindow, geolocation: geolocationData, @@ -2632,6 +2638,7 @@ export const SpectrumSidebar: React.FC = ({ captureDurationMode, captureDurationS, captureFileTypeState, + isMockSource, acquisitionMode, maxSampleRate, liveDeviceProfileToUse?.kind, @@ -3040,14 +3047,17 @@ export const SpectrumSidebar: React.FC = ({ dispatch(setTxHopEnabled(false)); handleToggleTransmitMode(id, false); } - // Leaving Tx is an explicit Rx handoff. Do not invert the - // backend's possibly stale paused flag in that branch. For a - // normal Rx card, preserve the existing Pause/Resume toggle. + // Leaving Tx is an explicit Rx handoff: the transition contract + // says whether Rx comes back held paused. Setting that latch here + // keeps the pill label and the canvas banner in agreement with the + // frozen stream on the first render. For a normal Rx card, preserve + // the existing Pause/Resume toggle. if (transition?.actions.includes("request_rx_frame")) { + const landPaused = transition.actions.includes("pause_rx"); if (setLiveVisualizerPause) { - setLiveVisualizerPause(false, id, "rx"); + setLiveVisualizerPause(landPaused, id, "rx"); } else { - spectrumTransport.sendPauseCommand(false, id, "rx"); + spectrumTransport.sendPauseCommand(landPaused, id, "rx"); } } else { toggleLiveVisualizerPause(id); @@ -3157,6 +3167,7 @@ export const SpectrumSidebar: React.FC = ({ captureDurationMode={captureDurationMode} captureDurationS={captureDurationS} captureFileType={captureFileTypeState} + isMockSource={isMockSource} acquisitionMode={acquisitionMode} captureEncrypted={captureEncrypted} capturePlayback={capturePlayback} diff --git a/src/ts/redux/middleware/websocketMiddleware.ts b/src/ts/redux/middleware/websocketMiddleware.ts index d52087ec..837a22a8 100644 --- a/src/ts/redux/middleware/websocketMiddleware.ts +++ b/src/ts/redux/middleware/websocketMiddleware.ts @@ -4452,6 +4452,13 @@ const createWebSocketMiddleware = pendingManagedTxOptions = previewOptions; pendingManagedTxPreviewOptions = previewOptions as ManagedTxStreamOptions; + // A status transition can tear this subscription down and reopen + // it while the option update is still unacknowledged. That + // rejects the in-flight update, so awaiting it before arming the + // one-shot would drop the request and leave the Tx canvas on its + // loading placeholder. Arm the source-scoped request up front so + // the replacement subscription replays it on hydration. + pendingManagedTxFrameRequestSourceId = requestedSourceId; const previewSubscription = managedTxSubscription; void previewSubscription .updateOptions(previewOptions) @@ -4464,6 +4471,7 @@ const createWebSocketMiddleware = managedTxSubscription === previewSubscription && managedTxSourceId === requestedSourceId ) { + pendingManagedTxFrameRequestSourceId = null; previewSubscription.requestNextFrame(); } }) @@ -4706,7 +4714,6 @@ const createWebSocketMiddleware = ? managedTxSubscription : null; if (activeSubscription) { - pendingManagedTxFrameRequestSourceId = null; void activeSubscription .updateOptions(previewOptions) .then(() => { @@ -4714,6 +4721,7 @@ const createWebSocketMiddleware = managedTxSubscription === activeSubscription && managedTxSourceId === sourceId ) { + pendingManagedTxFrameRequestSourceId = null; activeSubscription.requestNextFrame(); } }) diff --git a/src/ts/shared/ui/FrequencyInput.tsx b/src/ts/shared/ui/FrequencyInput.tsx index 80428dc5..c376b1fe 100644 --- a/src/ts/shared/ui/FrequencyInput.tsx +++ b/src/ts/shared/ui/FrequencyInput.tsx @@ -164,7 +164,13 @@ interface FrequencyInputProps { placeholder?: string; autoFocus?: boolean; commitOnBlur?: boolean; - onBlur?: () => void; + /** + * Called when the input loses focus. Receives the draft value parsed from the + * field (`null` when the field was left empty or unparseable) so a parent can + * distinguish "cleared" from "typed a value" — an emptied field emits no + * `onChangeHz` at all. + */ + onBlur?: (draftHz: number | null) => void; onKeyDown?: (event: React.KeyboardEvent) => void; disabled?: boolean; className?: string; @@ -413,6 +419,12 @@ export const FrequencyInput: React.FC = React.memo( ); }; + const resolveDraftHz = (): number | null => { + const parsed = parseFloat(displayValue.replace(/\s+/g, "")); + if (!Number.isFinite(parsed)) return null; + return parsed * getFrequencyUnitScale(displayUnit as any); + }; + const handleContainerBlur = (e: React.FocusEvent): void => { if (disabled) return; @@ -424,18 +436,19 @@ export const FrequencyInput: React.FC = React.memo( setIsUnitMenuOpen(false); isFocusedRef.current = false; + // Capture the draft before the display is reset so the parent learns what + // the field actually held (empty clears emit no onChangeHz). + const draftHz = resolveDraftHz(); + if (commitOnBlur) { - const val = parseFloat(displayValue.replace(/\s+/g, "")); - if (Number.isFinite(val)) { - const multiplier = getFrequencyUnitScale(displayUnit as any); - const newHz = val * multiplier; - handleUpdate(newHz, true); + if (draftHz !== null) { + handleUpdate(draftHz, true); } else { const { value, unit } = getOptimalFrequencyScale(hzRef.current); setDisplayValue(trimNumericString(formatFrequencyValue(value))); setDisplayUnit(unit); } - onBlur?.(); + onBlur?.(draftHz); return; } @@ -446,7 +459,7 @@ export const FrequencyInput: React.FC = React.memo( ? formatFrequencyHz(hzRef.current) : trimNumericString(formatFrequencyValue(value)), ); - onBlur?.(); + onBlur?.(draftHz); }; const unitButtonRef = useRef(null); diff --git a/src/ts/validation/schemas.ts b/src/ts/validation/schemas.ts index 97c1672b..fa28fffb 100644 --- a/src/ts/validation/schemas.ts +++ b/src/ts/validation/schemas.ts @@ -233,6 +233,7 @@ export const SourceInfoSchema = z.object({ id: z.string(), name: z.string(), kind: z.string(), + is_mock: z.boolean().optional(), capability: SourceCapabilitySchema, duplex_mode: z.string().nullable().optional(), active_duplex_mode: DeviceActiveModeSchema.nullable().optional(), diff --git a/src/ts/webusb/iqCapture.worker.ts b/src/ts/webusb/iqCapture.worker.ts index 20313464..d21372f5 100644 --- a/src/ts/webusb/iqCapture.worker.ts +++ b/src/ts/webusb/iqCapture.worker.ts @@ -120,7 +120,7 @@ const completeCapture = async (state: CaptureState): Promise => { const metadata = makeFinalMetadata(state); let bytes: Uint8Array; if (state.format === ".iq") { - bytes = encodeIqCaptureV4({ + bytes = await encodeIqCaptureV4({ metadata, frameUpdates: state.frameUpdates, chunks: state.chunks, diff --git a/src/ts/webusb/iqCaptureFormat.ts b/src/ts/webusb/iqCaptureFormat.ts index f55ea838..ac462504 100644 --- a/src/ts/webusb/iqCaptureFormat.ts +++ b/src/ts/webusb/iqCaptureFormat.ts @@ -29,6 +29,13 @@ const TRAILER_MAGIC = new TextEncoder().encode("NAPTTRLR"); const TRAILER_HEADER_SIZE = 24; const PBKDF2_ITERATIONS = 100_000; const PBKDF2_SALT = new TextEncoder().encode("n-apt-aes-salt-v1"); +import { + NAPT_FORMAT_VERSION, + NAPT_TRAILER_VERSION, + INTEGRITY_SCOPE, + integrityPlaceholder, + stampIntegrity, +} from "./iqIntegrity"; type CaptureBytes = Uint8Array; const copyBytes = (bytes: Uint8Array): CaptureBytes => { @@ -82,7 +89,7 @@ const base64 = (bytes: Uint8Array): string => { const createIqMetadata = (metadata: CaptureMetadata): CaptureMetadata => ({ format: "iq", - format_version: 4, + format_version: NAPT_FORMAT_VERSION, interleaving: "IQ", sample_encoding: { element_type: "integer", @@ -114,7 +121,7 @@ const encodeIqPayload = ( return concatBytes(...parts); }; -export const encodeIqCaptureV4 = ({ +export const encodeIqCaptureV4 = async ({ metadata, frameUpdates, chunks, @@ -124,14 +131,20 @@ export const encodeIqCaptureV4 = ({ frameUpdates: IqCaptureFrameUpdate[]; chunks: IqCaptureChunk[]; privateMetadata?: Record; -}): Uint8Array => { +}): Promise => { const metadataObject = createIqMetadata({ ...metadata, encrypted: false }); const framesBytes = utf8(JSON.stringify(frameUpdates)); const payload = encodeIqPayload(chunks, privateMetadata); let binaryOffset = 0; let trailerOffset = 0; let metadataBytes = new Uint8Array(0); - const trailerBytes = utf8("{}"); + const trailerBytes = utf8(JSON.stringify({ + integrity: { + algorithm: "SHA-256", + scope: INTEGRITY_SCOPE, + digest: integrityPlaceholder(), + }, + })); for (let attempt = 0; attempt < 8; attempt += 1) { metadataObject.sections = { @@ -145,7 +158,7 @@ export const encodeIqCaptureV4 = ({ offset_bytes: trailerOffset, length_bytes: TRAILER_HEADER_SIZE + trailerBytes.byteLength, encoding: "utf8_json", - version: 1, + version: NAPT_TRAILER_VERSION, }, }; metadataBytes = utf8(JSON.stringify(metadataObject)); @@ -175,7 +188,7 @@ export const encodeIqCaptureV4 = ({ writeU64(trailerBytes.byteLength), trailerBytes, ); - return header; + return stampIntegrity(header); }; export const decodeIqCaptureHeader = ( @@ -301,14 +314,22 @@ export const encodeNaptCaptureV4 = async ({ const metadataObject: CaptureMetadata = { ...metadata, format: "napt", - format_version: 4, + format_version: NAPT_FORMAT_VERSION, encrypted: true, interleaving: "IQ", channels: channelMetadata, wrapped_dek: base64(wrappedDek), }; const trailerJson = utf8( - JSON.stringify({ processing: { operation: "capture" }, tool_version: "0.5.0" }), + JSON.stringify({ + processing: { operation: "capture" }, + tool_version: "0.5.0", + integrity: { + algorithm: "SHA-256", + scope: INTEGRITY_SCOPE, + digest: integrityPlaceholder(), + }, + }), ); let headerSize = Math.max( 4096, @@ -328,7 +349,7 @@ export const encodeNaptCaptureV4 = async ({ offset_bytes: trailerOffset, length_bytes: TRAILER_HEADER_SIZE + trailerJson.byteLength, encoding: "utf8_json", - version: 1, + version: NAPT_TRAILER_VERSION, }, }; completeJson = JSON.stringify({ metadata: metadataObject }); @@ -342,7 +363,7 @@ export const encodeNaptCaptureV4 = async ({ const headerBytes = utf8(completeJson); const padding = new Uint8Array(Math.max(0, headerSize - headerBytes.byteLength - 1)); padding.fill(0x20); - return concatBytes( + return stampIntegrity(concatBytes( headerBytes, Uint8Array.of(0x0a), padding, @@ -352,5 +373,5 @@ export const encodeNaptCaptureV4 = async ({ new Uint8Array(7), writeU64(trailerJson.byteLength), trailerJson, - ); + )); }; diff --git a/src/ts/webusb/iqIntegrity.ts b/src/ts/webusb/iqIntegrity.ts new file mode 100644 index 00000000..162784f5 --- /dev/null +++ b/src/ts/webusb/iqIntegrity.ts @@ -0,0 +1,45 @@ +const PLACEHOLDER = "0".repeat(64); +const encoder = new TextEncoder(); + +export const NAPT_FORMAT_VERSION = 5; +export const NAPT_TRAILER_VERSION = 2; +export const INTEGRITY_SCOPE = "file-with-integrity-digest-placeholder"; + +const toHex = (bytes: Uint8Array): string => + Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); + +const findToken = (bytes: Uint8Array, token: string): number => { + const marker = encoder.encode(token); + outer: for (let offset = 0; offset <= bytes.length - marker.length; offset += 1) { + for (let index = 0; index < marker.length; index += 1) { + if (bytes[offset + index] !== marker[index]) continue outer; + } + return offset; + } + return -1; +}; + +export const stampIntegrity = async (bytes: Uint8Array): Promise => { + const placeholderOffset = findToken(bytes, PLACEHOLDER); + if (placeholderOffset < 0) throw new Error("Integrity digest placeholder is missing"); + const digestInput = bytes.slice(); + const digest = toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", digestInput.buffer))); + const result = bytes.slice(); + result.set(encoder.encode(digest), placeholderOffset); + return result; +}; + +export const verifyStampedIntegrity = async ( + bytes: Uint8Array, + digest: string, +): Promise => { + if (!/^[0-9a-f]{64}$/.test(digest)) return false; + const placeholderOffset = findToken(bytes, digest); + if (placeholderOffset < 0) return false; + const normalized = bytes.slice(); + normalized.set(encoder.encode(PLACEHOLDER), placeholderOffset); + const calculated = toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", normalized.buffer))); + return calculated === digest; +}; + +export const integrityPlaceholder = (): string => PLACEHOLDER; diff --git a/src/ts/workers/fileWorker.ts b/src/ts/workers/fileWorker.ts index c750845b..19199a6b 100644 --- a/src/ts/workers/fileWorker.ts +++ b/src/ts/workers/fileWorker.ts @@ -5,6 +5,7 @@ import { parseFrequency } from "@n-apt/math/frequency"; import { base64ToBytes } from "@n-apt/crypto/webcrypto"; import { BYTES_PER_IQ_SAMPLE } from "@n-apt/math/signalData"; +import { verifyStampedIntegrity } from "@n-apt/webusb/iqIntegrity"; let currentFftSize = 8192; @@ -83,6 +84,30 @@ type FileMetadata = { }[]; }; +type IntegrityStatus = "verified" | "failed" | "unavailable"; + +const verifyFileIntegrity = async ( + fileData: ArrayBuffer, + metadata: FileMetadata, +): Promise => { + if ((metadata.format_version ?? 3) < 5) return "unavailable"; + const trailer = metadata.trailer as { integrity?: { + algorithm?: string; + scope?: string; + digest?: string; + } } | null; + const integrity = trailer?.integrity; + if ( + !integrity || + integrity.algorithm !== "SHA-256" || + integrity.scope !== "file-with-integrity-digest-placeholder" || + !integrity.digest + ) return "failed"; + return (await verifyStampedIntegrity(new Uint8Array(fileData), integrity.digest)) + ? "verified" + : "failed"; +}; + type WavLoadResult = { raw: Uint8Array; metadata: FileMetadata | null; @@ -136,6 +161,9 @@ async function loadIqFile( const trailerLength = Number(new DataView(trailerBytes.buffer, trailerBytes.byteOffset + 16, 8).getBigUint64(0, true)); if (trailerLength + 24 !== trailerBytes.length) throw new Error("Invalid IQ v4 trailer length"); metadata.trailer = JSON.parse(new TextDecoder().decode(trailerBytes.slice(24))); + if (trailerSection.version === 2 && (await verifyFileIntegrity(fileData, metadata)) === "failed") { + throw new Error("INTEGRITY_FAILED: file appears corrupted or modified"); + } } const frameUpdates = JSON.parse(new TextDecoder().decode( new Uint8Array(fileData, framesStart, framesLength), @@ -622,7 +650,7 @@ self.onmessage = async function (e) { try { switch (type) { case "loadFile": { - const { fileData, fileName, aesKey: rawAesKey } = data; + const { fileData, fileName, aesKey: rawAesKey, allowIntegrityFailure = false } = data; const aesKey = rawAesKey ? await crypto.subtle.importKey( "raw", @@ -723,6 +751,9 @@ self.onmessage = async function (e) { const trailerJsonLength = Number(new DataView(trailerBytes.buffer, trailerBytes.byteOffset + 16, 8).getBigUint64(0, true)); if (trailerJsonLength + 24 !== trailerBytes.length) throw new Error("Invalid NAPT v4 trailer length"); metadata.trailer = JSON.parse(new TextDecoder().decode(trailerBytes.slice(24))); + if (!allowIntegrityFailure && (await verifyFileIntegrity(fileData, metadata)) === "failed") { + throw new Error("INTEGRITY_FAILED: file appears corrupted or modified"); + } naptBinaryFileData = fileData.slice(0, trailer.offset_bytes); } const isEncrypted = @@ -847,6 +878,7 @@ self.onmessage = async function (e) { fftSize, aesKey: rawAesKey, sampleRateOptions, + allowIntegrityFailure, } = data; let aesKey: CryptoKey | null = null; @@ -994,6 +1026,9 @@ self.onmessage = async function (e) { const trailerJsonLength = Number(new DataView(trailerBytes.buffer, trailerBytes.byteOffset + 16, 8).getBigUint64(0, true)); if (trailerJsonLength + 24 !== trailerBytes.length) throw new Error("Invalid NAPT v4 trailer length"); metadata.trailer = JSON.parse(new TextDecoder().decode(trailerBytes.slice(24))); + if (!allowIntegrityFailure && (await verifyFileIntegrity(file.fileData, metadata)) === "failed") { + throw new Error("INTEGRITY_FAILED: file appears corrupted or modified"); + } naptBinaryFileData = file.fileData.slice(0, trailer.offset_bytes); } const isEncrypted = !!( diff --git a/src/ts/workers/fileWorkerManager.ts b/src/ts/workers/fileWorkerManager.ts index dd93e97a..dd29b189 100644 --- a/src/ts/workers/fileWorkerManager.ts +++ b/src/ts/workers/fileWorkerManager.ts @@ -158,6 +158,7 @@ export class FileWorkerManager { maxSampleRateHz: number; currentSampleRateHz: number; }, + allowIntegrityFailure = false, ): Promise { const filesData = []; @@ -180,6 +181,7 @@ export class FileWorkerManager { settings, fftSize, aesKey: aesKey ? await crypto.subtle.exportKey("raw", aesKey) : null, + allowIntegrityFailure, sampleRateOptions, // Pass current sample rate options dynamically }, onProgress, diff --git a/test/integration/helpers/liveReduxStreamHarness.ts b/test/integration/helpers/liveReduxStreamHarness.ts index 58ff0ab0..48aef42e 100644 --- a/test/integration/helpers/liveReduxStreamHarness.ts +++ b/test/integration/helpers/liveReduxStreamHarness.ts @@ -126,6 +126,14 @@ export type LiveReduxStreamHarness = { simulateHardwarePresence(present: boolean): Promise; setFftSize(fftSize: number, timeoutMs?: number): Promise; retuneCenterFrequency(centerHz: number): Promise; + /** + * Block until the managed stream topology stops changing: both mode + * subscriptions are open and the stream epochs, presentation target and + * lifecycle phase have held steady across consecutive polls. Tests use this + * as a barrier so a late transition from one scenario cannot land in the + * middle of the next one. + */ + quiesce(timeoutMs?: number): Promise; waitFor( read: () => T, predicate: (value: T) => boolean, @@ -713,6 +721,42 @@ export const createLiveReduxStreamHarness = async ( return performance.now() - startedAt; }, + async quiesce(timeoutMs = 15_000) { + const deadline = Date.now() + timeoutMs; + // A request_next_frame answer is a whole epoch away: the backend opens a + // stream, publishes one frame and can be superseded by the next request. + // Requiring a sustained quiet window rather than two adjacent polls keeps + // a straggler from that request out of the next scenario. + const requiredStableMs = 300; + let previousTopology: string | null = null; + let stableSince = 0; + while (Date.now() < deadline) { + const snapshot = harness.snapshot(); + const subscriptionsOpen = + !snapshot.managed.rx.subscribePending && + !snapshot.managed.tx.subscribePending; + // Deliberately excludes frame sequences: a streaming source advances + // them forever without the topology changing. + const topology = JSON.stringify({ + rxEpoch: snapshot.managed.rx.streamEpoch, + txEpoch: snapshot.managed.tx.streamEpoch, + target: snapshot.presentationTarget, + phase: snapshot.lifecycle.phase, + }); + const now = Date.now(); + if (subscriptionsOpen && topology === previousTopology) { + if (now - stableSince >= requiredStableMs) return; + } else { + stableSince = now; + } + previousTopology = topology; + await sleep(pollIntervalMs); + } + throw new Error( + "Timed out waiting for the live stream harness to quiesce", + ); + }, + async waitFor(read, predicate, timeoutMs = 15_000) { const deadline = Date.now() + timeoutMs; let value = read(); diff --git a/test/integration/live-redux-stream.integration.test.ts b/test/integration/live-redux-stream.integration.test.ts index c46ae6d6..1c772284 100644 --- a/test/integration/live-redux-stream.integration.test.ts +++ b/test/integration/live-redux-stream.integration.test.ts @@ -47,6 +47,33 @@ describe("live Redux/source-mode stream harness", () => { harness?.close(); }); + // Every scenario below drives global state: the shared Tx transmitter, the + // subscriber pause, and this client's view handoff. A transition that was + // still in flight when a scenario ended would otherwise land inside the next + // one, which is what turned a single race into a multi-test cascade. Return + // to one baseline and let the stream topology settle before each scenario. + const resetToBaseline = async () => { + const snapshot = harness.snapshot(); + if (snapshot.redux.sourceStatuses[MOCK_TX_SOURCE_ID] === "transmitting") { + await harness.setTransmit(false, MOCK_TX_SOURCE_ID); + } + if (snapshot.sourcePause[MOCK_APT_SOURCE_ID]) { + await harness.setPaused(false, MOCK_APT_SOURCE_ID); + } + // Only re-home the view when something left it elsewhere, so the first + // scenarios still exercise the initial selection the harness connected + // with instead of one this hook made. + const target = harness.snapshot().presentationTarget; + if (target.sourceId !== MOCK_APT_SOURCE_ID || target.mode !== "rx") { + await harness.selectSource(MOCK_APT_SOURCE_ID); + await harness.viewSource(MOCK_APT_SOURCE_ID); + } + await waitForMockAptStreaming(harness); + await harness.quiesce(); + }; + + beforeEach(resetToBaseline); + test("loads both built-in mock sources on control-plane connect", async () => { const snapshot = harness.snapshot(); expect(snapshot.hasConnectedOnce).toBe(true); @@ -398,9 +425,18 @@ describe("live Redux/source-mode stream harness", () => { // used to briefly replace the accepted preview with the full Loading UI. await new Promise((resolve) => setTimeout(resolve, 150)); const settled = harness.snapshot(); - expect(settled.txPresentation.sequence).toBeGreaterThanOrEqual( - preview.txPresentation.sequence!, - ); + // Every request_next_frame opens a fresh stream epoch, so a later preview + // legitimately restarts its sequence counter. Compare epochs first and only + // fall back to sequence within one epoch — the same ordering the + // varied-geometry scenario below relies on. + const settledIsAtLeastPreview = + (settled.txPresentation.streamEpoch ?? 0) > + (preview.txPresentation.streamEpoch ?? 0) || + ((settled.txPresentation.streamEpoch ?? 0) === + (preview.txPresentation.streamEpoch ?? 0) && + (settled.txPresentation.sequence ?? 0) >= + (preview.txPresentation.sequence ?? 0)); + expect(settledIsAtLeastPreview).toBe(true); expect(settled.lifecycle.phase).toBe("standby"); expect(settled.lifecycle.placeholderKind).toBe("top-bar"); }); diff --git a/test/rust/hackrf_integration_tests.rs b/test/rust/hackrf_integration_tests.rs index b535f9bd..8a62a931 100644 --- a/test/rust/hackrf_integration_tests.rs +++ b/test/rust/hackrf_integration_tests.rs @@ -24,4 +24,33 @@ mod hackrf_integration_tests { } Ok(()) } + + /// A live VFO retune must retune in place: no RX stop/restart, and the next + /// read must carry new samples instead of the reader being stranded stopped. + #[test] + fn live_retune_keeps_the_reader_streaming() -> Result<()> { + let device = HackRfDevice::open_first(); + let Ok(mut dev) = device else { + eprintln!("Skipping live HackRF One retune exercise: no device attached"); + return Ok(()); + }; + + dev.set_sample_rate(3_200_000)?; + dev.set_center_frequency(100_000_000)?; + dev.initialize()?; + + let first = dev.read_samples(2048)?; + assert!(!first.data.is_empty(), "initial HackRF RX frame was empty"); + + dev.set_center_frequency_live(101_000_000)?; + assert_eq!(dev.get_center_frequency(), 101_000_000); + let second = dev.read_samples(2048)?; + assert!( + !second.data.is_empty(), + "RX stream did not continue after a live retune" + ); + + dev.cleanup()?; + Ok(()) + } } diff --git a/test/rust/websocket_server_tests.rs b/test/rust/websocket_server_tests.rs index 172859ae..2dc85fc7 100644 --- a/test/rust/websocket_server_tests.rs +++ b/test/rust/websocket_server_tests.rs @@ -312,6 +312,7 @@ fn source_info_snapshot_includes_mock_tx_device() { assert_eq!(mock_tx["name"], "Mock Tx SDR"); assert_eq!(mock_tx["kind"], "mock_tx"); + assert_eq!(mock_tx["is_mock"], true); assert_eq!(mock_tx["capability"], "tx"); assert_eq!(mock_tx["status"], "connected"); assert_eq!(mock_tx["product"], "Mock Tx SDR"); diff --git a/test/ts/FFTCanvas.test.tsx b/test/ts/FFTCanvas.test.tsx index 2f4fa619..c15335fc 100644 --- a/test/ts/FFTCanvas.test.tsx +++ b/test/ts/FFTCanvas.test.tsx @@ -1,5 +1,5 @@ /** @jest-environment jsdom */ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import FFTCanvas from "@n-apt/spectrum/FFTCanvas"; import type { FFTCanvasHandle } from "@n-apt/spectrum/FFTCanvas"; @@ -151,6 +151,12 @@ jest.mock("@n-apt/spectrum/hooks/useWebGPUInit", () => ({ }), })); +// Captured so a test can drive one render pass without changing props, which +// is how a steady paused canvas is repainted in the running app. +export const mockCoordinatorForceRenderRefs: Array<{ + current: (() => void) | null; +}> = []; + jest.mock("@n-apt/spectrum/hooks/useFftRenderCoordinator", () => { const React = require("react") as typeof import("react"); return { @@ -166,7 +172,10 @@ jest.mock("@n-apt/spectrum/hooks/useFftRenderCoordinator", () => { [onRenderFrame], ); React.useEffect(() => { - if (forceRenderRef) forceRenderRef.current = render; + if (forceRenderRef) { + forceRenderRef.current = render; + mockCoordinatorForceRenderRefs.push(forceRenderRef); + } render(); return () => { if (forceRenderRef?.current === render) forceRenderRef.current = null; @@ -1023,6 +1032,70 @@ describe("FFTCanvas Component", () => { } }, 10000); + it("repaints a paused file-playback preview frame from its retained waveform", async () => { + // File playback disables live pause-snapshot recovery, so nothing else + // repaints the paused canvas. Overlay chrome that is invalidated after the + // last pass — the grid, axes and labels live in an overlay texture that is + // only re-uploaded from a draw — must still come back while the preview + // frame sits paused. Otherwise the frame keeps its waveform and background + // with no graph until playback resumes. + drawSpectrumMock.mockClear(); + processIqToDbmSpectrumMock.mockClear(); + mockCoordinatorForceRenderRefs.length = 0; + + const playbackFrame = { + source_id: "file-playback", + iq_data: new Uint8Array([128, 129, 127, 126]), + center_frequency_hz: 2_186_000, + sample_rate: 4_372_000, + }; + const dataRef = { current: playbackFrame as any }; + const renderCanvas = ( + props: Partial>, + ) => ( + + + + + + + + + + ); + + const { rerender } = render(renderCanvas({ isPaused: false })); + await waitFor(() => expect(drawSpectrumMock).toHaveBeenCalled()); + + // Stitching a file always lands paused: the preview frame is frozen, with + // no further frames arriving to drive the next pass. + rerender(renderCanvas({ isPaused: true })); + await waitFor(() => expect(drawSpectrumMock).toHaveBeenCalled()); + + drawSpectrumMock.mockClear(); + const forceRender = + mockCoordinatorForceRenderRefs[ + mockCoordinatorForceRenderRefs.length - 1 + ]?.current; + expect(forceRender).toBeTruthy(); + + // One steady paused repaint with no prop change and no new frame: the + // retained preview frame must still be served, and it must not be stale. + act(() => { + forceRender?.(); + }); + + expect(drawSpectrumMock).toHaveBeenCalled(); + }, 10000); + it("draws Mock Tx standby preview spectrum unchanged from backend I/Q processing", async () => { drawSpectrumMock.mockClear(); processIqToDbmSpectrumMock.mockClear(); diff --git a/test/ts/IQCaptureControlsSection.test.tsx b/test/ts/IQCaptureControlsSection.test.tsx index a3424628..c9a73250 100644 --- a/test/ts/IQCaptureControlsSection.test.tsx +++ b/test/ts/IQCaptureControlsSection.test.tsx @@ -12,6 +12,15 @@ jest.mock("@n-apt/app/hooks/useAuthentication", () => ({ }), })); +jest.mock("@n-apt/maps/public/useGeolocation", () => ({ + useGeolocation: () => ({ + isSupported: true, + requestPermission: jest.fn().mockResolvedValue(true), + error: null, + isLoading: false, + }), +})); + const defaultProps = { activeCaptureAreas: [], availableCaptureAreas: [{ label: "Area A", min: 10, max: 20 }], @@ -253,6 +262,41 @@ describe("IQCaptureControlsSection", () => { expect(screen.getByRole("option", { name: "Whole Sample" })).toBeInTheDocument(); }); + it("does not offer encrypted .napt captures for mock sources", () => { + render( + + + , + ); + + fireEvent.click(screen.getByText("Take an I/Q Capture")); + + expect(screen.queryByRole("option", { name: /^\.napt/ })).toBeDisabled(); + expect(screen.getByRole("option", { name: ".iq" })).toBeEnabled(); + expect(screen.getByRole("option", { name: ".wav" })).toBeEnabled(); + expect(screen.getAllByRole("checkbox")[0]).toBeDisabled(); + expect(screen.getAllByRole("checkbox")[0]).not.toBeChecked(); + }); + + it("keeps geolocation available for every capture format", () => { + render( + + + , + ); + + fireEvent.click(screen.getByText("Take an I/Q Capture")); + + expect(screen.getByText("Geolocation")).toBeInTheDocument(); + expect(screen.getAllByRole("checkbox")[1]).toBeEnabled(); + }); + it("should handle duration change", () => { render( diff --git a/test/ts/SourceSettingsSection.hackrf.test.tsx b/test/ts/SourceSettingsSection.hackrf.test.tsx index 568f86da..c01f7a6e 100644 --- a/test/ts/SourceSettingsSection.hackrf.test.tsx +++ b/test/ts/SourceSettingsSection.hackrf.test.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { fireEvent, render, screen, within } from "@testing-library/react"; +import { act, fireEvent, render, screen, within } from "@testing-library/react"; import { SourceSettingsSection } from "@n-apt/spectrum/sidebar/SourceSettingsSection"; import { TestWrapper } from "./testUtils"; @@ -88,6 +88,53 @@ describe("SourceSettingsSection HackRF controls", () => { expect(onHackrfBasebandBandwidthChange).toHaveBeenLastCalledWith(3_200_000); }); + it("holds the baseband filter off instead of letting auto-tracking switch it back on", () => { + const onPinnedChange = jest.fn(); + const onBasebandChange = jest.fn(); + + render( + + + , + ); + + const basebandRow = screen.getByText("Baseband filter").closest("div") + ?.parentElement as HTMLElement; + fireEvent.click(within(basebandRow).getByRole("checkbox")); + + // Off must pin the disabled state. Without the pin the auto-tracking effect + // sees 0 !== sampleRate and switches the filter straight back on, which made + // the toggle look dead until a custom value was typed. + expect(onPinnedChange).toHaveBeenLastCalledWith(true); + expect(onBasebandChange).toHaveBeenLastCalledWith(0); + }); + it("shows a baseband warning when the filter is narrower than the sample rate", () => { render( @@ -518,9 +565,117 @@ describe("SourceSettingsSection HackRF controls", () => { fireEvent.change(basebandInput, { target: { value: "2.4" } }); expect(onPinnedChange).toHaveBeenLastCalledWith(true); - // Clearing to zero resumes auto-tracking. + // Clearing the field resumes automatic tracking. It must not publish a zero + // width (that is what failed the filter set and tore the live stream down); + // the derived sample-rate value repopulates the field on blur. rerender(section(true)); fireEvent.change(basebandInput, { target: { value: "0" } }); expect(onPinnedChange).toHaveBeenLastCalledWith(false); + expect(onBasebandChange).not.toHaveBeenCalledWith(0); + }); + + it("shows the active sample rate while the filter is following it", () => { + render( + + + , + ); + + const basebandRow = screen.getByText("Baseband filter").closest("div") + ?.parentElement as HTMLElement; + + expect(within(basebandRow).getByRole("textbox")).toHaveValue("3.2"); + }); + + it("repopulates the sample rate when a cleared baseband field is blurred", () => { + const pinnedCalls: boolean[] = []; + const Harness = () => { + const [pinned, setPinned] = React.useState(true); + const [value, setValue] = React.useState(2_400_000); + return ( + { + pinnedCalls.push(next); + setPinned(next); + }} + tunerAGC={false} + rtlAGC={false} + stitchSourceSettings={{ gain: 0, ppm: 0 }} + isConnected={true} + onPpmChange={jest.fn()} + onGainChange={jest.fn()} + onHackrfLnaGainChange={jest.fn()} + onHackrfVgaGainChange={jest.fn()} + onHackrfAmpEnabledChange={jest.fn()} + onHackrfBasebandBandwidthChange={setValue} + onTunerAGCChange={jest.fn()} + onRtlAGCChange={jest.fn()} + onStitchSourceSettingsChange={jest.fn()} + onAgcModeChange={jest.fn()} + /> + ); + }; + + render( + + + , + ); + + const basebandRow = screen.getByText("Baseband filter").closest("div") + ?.parentElement as HTMLElement; + const basebandInput = within(basebandRow).getByRole("textbox"); + expect(basebandInput).toHaveValue("2.4"); + + // Clearing the field must resolve back to the current sample rate, not the + // previous custom value. An emptied field emits no change event at all, so + // the pin is released on blur. + act(() => { + basebandInput.focus(); + }); + fireEvent.change(basebandInput, { target: { value: "" } }); + act(() => { + basebandInput.blur(); + }); + + expect(pinnedCalls[pinnedCalls.length - 1]).toBe(false); + // The field is remounted on blur, so re-query rather than reusing the node. + expect(within(basebandRow).getByRole("textbox")).toHaveValue("3.2"); }); }); diff --git a/test/ts/SpectrumRoute.frequencyRange.test.ts b/test/ts/SpectrumRoute.frequencyRange.test.ts index e88e1cc2..c363061e 100644 --- a/test/ts/SpectrumRoute.frequencyRange.test.ts +++ b/test/ts/SpectrumRoute.frequencyRange.test.ts @@ -21,7 +21,7 @@ describe("subscriber-local visual pan", () => { }); describe("createLiveFrequencyRangePublisher", () => { - it("publishes a leading pan and throttles the latest value to 20Hz", () => { + it("publishes a leading pan and throttles the latest value to one frame", () => { jest.useFakeTimers(); const setFrequencyRange = jest.fn(); const sendFrequencyRange = jest.fn(); @@ -41,7 +41,8 @@ describe("createLiveFrequencyRangePublisher", () => { expect(sendFrequencyRange).toHaveBeenCalledTimes(1); expect(sendFrequencyRange).toHaveBeenCalledWith(firstRange); - jest.advanceTimersByTime(50); + // One display frame, not the old 50 ms / 20 Hz cap. + jest.advanceTimersByTime(16); expect(setFrequencyRange).toHaveBeenCalledTimes(2); expect(setFrequencyRange).toHaveBeenLastCalledWith(latestRange); @@ -143,24 +144,19 @@ describe("publishFrequencyRangeBySource", () => { describe("resolveNavigationFrequencyBounds", () => { const channelBounds = { min: 18_000, max: 4_390_000 }; - const hardwareBounds = { min: 0, max: 30_000_000 }; + const hardwareBounds = { min: 0, max: 6_000_000_000 }; - it("lets mirror-mode display navigation acquire beyond the active channel", () => { - expect( - resolveNavigationFrequencyBounds({ - channelBounds, - hardwareBounds, - }), - ).toEqual(hardwareBounds); + it("uses the hydrated device bounds", () => { + expect(resolveNavigationFrequencyBounds({ hardwareBounds })).toEqual( + hardwareBounds, + ); }); - it("keeps the device acquisition bounds at 1x so every subscriber can reach the same edge", () => { - expect( - resolveNavigationFrequencyBounds({ - channelBounds, - hardwareBounds, - }), - ).toEqual(hardwareBounds); + it("falls back to the global spectrum bounds instead of the active channel", () => { + const bounds = resolveNavigationFrequencyBounds({ hardwareBounds: null }); + + expect(bounds).toEqual({ min: 0, max: 30_000_000_000 }); + expect(bounds).not.toEqual(channelBounds); }); }); diff --git a/test/ts/SpectrumSidebar.sampleRate.test.tsx b/test/ts/SpectrumSidebar.sampleRate.test.tsx index 3471cfc5..6352cd99 100644 --- a/test/ts/SpectrumSidebar.sampleRate.test.tsx +++ b/test/ts/SpectrumSidebar.sampleRate.test.tsx @@ -64,6 +64,8 @@ let mockSignalAreaBounds: Record | null; let mockWsConnection: any; let mockStoreDispatch: jest.Mock; let mockToggleVisualizerPause: jest.Mock; +let mockSetVisualizerPause: jest.Mock; +let mockManualVisualizerPaused: boolean | null; let mockShowPrompt: jest.Mock; let mockEffectiveSampleRateHz: number | null; @@ -117,8 +119,9 @@ jest.mock("@n-apt/spectrum/hooks/useSpectrumStore", () => ({ mockEffectiveSampleRateHz ?? mockLiveState.sampleRateHz, signalAreaBounds: mockSignalAreaBounds, wsConnection: mockWsConnection, - manualVisualizerPaused: null, + manualVisualizerPaused: mockManualVisualizerPaused, toggleVisualizerPause: mockToggleVisualizerPause, + setVisualizerPause: mockSetVisualizerPause, cryptoCorrupted: false, deviceName: mockWsConnection?.deviceName ?? "HackRF One", deviceProfile: mockWsConnection?.deviceProfile ?? { kind: "hackrf_one" }, @@ -164,7 +167,9 @@ jest.mock("@n-apt/spectrum/sidebar/SourceInput", () => ({ default: ({ devices = [], onSelectedDeviceChange, + onToggleDeviceRxPause, onToggleDeviceTxMode, + onPreviewDeviceTx, selectedDeviceId, onSourceModeChange, }: { @@ -177,7 +182,9 @@ jest.mock("@n-apt/spectrum/sidebar/SourceInput", () => ({ }; }>; onSelectedDeviceChange?: (id: string) => void; + onToggleDeviceRxPause?: (id: string) => void; onToggleDeviceTxMode?: (id: string) => void; + onPreviewDeviceTx?: (id: string) => void; selectedDeviceId?: string; onSourceModeChange?: (mode: "live" | "file") => void; }) => ( @@ -206,6 +213,27 @@ jest.mock("@n-apt/spectrum/sidebar/SourceInput", () => ({ {device.status.actionLabel} ) : null} + + +
))} @@ -342,6 +370,8 @@ const initMockState = () => { sendSettings: jest.fn(), sendFrequencyRange: jest.fn(), sendTransmitStatus: jest.fn(), + sendPauseCommand: jest.fn(), + sendPowerScaleCommand: jest.fn(), }; mockStoreDispatch = jest.fn((action: any) => { if (action?.type === "SET_SDR_SETTINGS_BUNDLE" && action.settings) { @@ -393,6 +423,8 @@ const initMockState = () => { } }); mockToggleVisualizerPause = jest.fn(); + mockSetVisualizerPause = jest.fn(); + mockManualVisualizerPaused = null; }; describe("SpectrumSidebar sample rate behavior", () => { @@ -2314,4 +2346,157 @@ describe("SpectrumSidebar sample rate behavior", () => { expect(store.getState().waterfall.sourceMode).toBe("file"); }); + + it("keeps a manual File Selection across a sidebar remount", () => { + const store = createStore(); + // Model the app already being in file mode opened from the sidebar, with no + // deep link in the URL. + mockLiveState = { ...mockLiveState, sourceMode: "file" }; + store.dispatch(setSourceMode("file")); + + const tree = () => ( + + + + + + + + ); + + const { unmount } = render(tree()); + expect(store.getState().waterfall.sourceMode).toBe("file"); + + // The shell remounts the sidebar on route/nav changes. A manual File + // Selection must survive so the user is not dropped back onto the first + // live source with their picked files still loaded. + unmount(); + render(tree()); + + expect(store.getState().waterfall.sourceMode).toBe("file"); + }); + + it("resets a deep-link-owned File Selection when it remounts without the deep link", () => { + const store = createStore(); + mockLiveState = { ...mockLiveState, sourceMode: "file" }; + store.dispatch(setSourceMode("file")); + + const tree = (entries: string[]) => ( + + + + + + + + ); + + const { unmount } = render(tree(["/?source=fileSelection"])); + expect(store.getState().waterfall.sourceMode).toBe("file"); + + // Remounting after the deep link is gone still returns to live sources. + unmount(); + render(tree(["/"])); + + expect(store.getState().waterfall.sourceMode).toBe("live"); + }); +}); + +describe("SpectrumSidebar Tx/Rx switch handlers", () => { + const buildHackRfSource = () => ({ + id: "hackrf-1", + kind: "hackrf_one", + backend: "hackrf", + name: "HackRF One", + deviceName: "HackRF One", + capability: "tx_rx", + duplex_mode: "Half-duplex", + status: "receiving", + sdr: { settings: { sample_rate: 3_200_000 } }, + }); + + const mountSidebar = async ( + source: { id: string } & Record, + ) => { + mockLiveState = { + ...mockLiveState, + selectedSourceId: source.id, + sourceMode: "live", + sources: [source], + }; + mockWsConnection = { ...mockWsConnection, sources: [source] }; + + const store = createStore(); + store.dispatch(setConnected()); + store.dispatch( + updateDeviceState({ + activeSourceId: source.id, + activeSourceMode: "live", + sources: [source], + } as any), + ); + + render( + + + + + + + , + ); + + await act(async () => { + await new Promise((r) => setTimeout(r, 50)); + }); + + return source; + }; + + const flush = async () => { + await act(async () => { + await new Promise((r) => setTimeout(r, 0)); + }); + }; + + beforeEach(() => { + initMockState(); + mockShowPrompt = jest.fn(); + window.localStorage.removeItem(TRANSMIT_WARNING_ACK_KEY); + }); + + it("lands Rx paused instead of reporting a live stream when a half-duplex HackRF leaves Tx", async () => { + const source = await mountSidebar(buildHackRfSource()); + + // Preview Tx binds the source to the Tx suite and enters Tx standby. + fireEvent.click(screen.getByTestId(`preview-tx-${source.id}`)); + await flush(); + + // Leaving Tx is an explicit Rx handoff. Resuming here paints "Pause Rx" + // (and hides the paused banner) over a stream the device has not restarted + // yet, which is the reported ghost state. + fireEvent.click(screen.getByTestId(`to-rx-${source.id}`)); + await flush(); + + expect(mockSetVisualizerPause).toHaveBeenCalledWith(true, source.id, "rx"); + expect(mockSetVisualizerPause).not.toHaveBeenCalledWith( + false, + source.id, + "rx", + ); + }); + + it("registers every rapid Tx start instead of throttling the switch", async () => { + // The Tx-suite binding guard deliberately gates a physical HackRF, so the + // throttle itself is exercised on a Tx source that reaches the control path + // directly. Every click must land as its own request. + const source = { id: "tx-1", kind: "mock_tx", capability: "tx" }; + await mountSidebar(source); + + fireEvent.click(screen.getByTestId(`to-tx-${source.id}`)); + fireEvent.click(screen.getByTestId(`to-tx-${source.id}`)); + await flush(); + + expect(mockShowPrompt).toHaveBeenCalledTimes(2); + }); }); diff --git a/test/ts/WebUsbIqCaptureFormat.test.ts b/test/ts/WebUsbIqCaptureFormat.test.ts index ba66362b..f24023b7 100644 --- a/test/ts/WebUsbIqCaptureFormat.test.ts +++ b/test/ts/WebUsbIqCaptureFormat.test.ts @@ -26,8 +26,8 @@ const metadata = { }; describe("standalone IQ capture containers", () => { - it("writes an app-compatible NAPT-IQ3 v4 container with chunk offsets", () => { - const bytes = encodeIqCaptureV4({ + it("writes an app-compatible NAPT-IQ3 v5 container with chunk offsets", async () => { + const bytes = await encodeIqCaptureV4({ metadata, frameUpdates: [ { @@ -45,7 +45,7 @@ describe("standalone IQ capture containers", () => { const decoded = decodeIqCaptureHeader(bytes); expect(decoded.metadata).toMatchObject({ format: "iq", - format_version: 4, + format_version: 5, interleaving: "IQ", center_frequency_hz: 1_600_000, capture_sample_rate_hz: 3_200_000, @@ -75,7 +75,7 @@ describe("standalone IQ capture containers", () => { expect(decoded.payload.slice(44)).toEqual(Uint8Array.of(130, 125)); }); - it("writes an encrypted v4 NAPT container with indexed binary data", async () => { + it("writes an encrypted v5 NAPT container with indexed binary data", async () => { const bytes = await encodeNaptCaptureV4({ metadata: { ...metadata, encrypted: true }, channels: [ @@ -98,7 +98,7 @@ describe("standalone IQ capture containers", () => { }; expect(root.metadata).toMatchObject({ format: "napt", - format_version: 4, + format_version: 5, encrypted: true, data_format: "iq_u8", channels: [{ offset_iq: 0, iq_length: 4, label: null }], diff --git a/test/ts/desktopNotify.test.ts b/test/ts/desktopNotify.test.ts new file mode 100644 index 00000000..357a7afa --- /dev/null +++ b/test/ts/desktopNotify.test.ts @@ -0,0 +1,207 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const mockSpawn = jest.fn(); +const errorHandlers: Array<(error: Error) => void> = []; + +jest.mock("node:child_process", () => ({ + spawn: (...args: unknown[]) => mockSpawn(...(args as [])), +})); + +const requireNotify = () => { + let mod: typeof import("../../scripts/build/desktopNotify"); + jest.isolateModules(() => { + mod = require("../../scripts/build/desktopNotify"); + }); + return mod!; +}; + +const originalPlatform = process.platform; +const originalPath = process.env.PATH; +const setPlatform = (platform: string) => { + Object.defineProperty(process, "platform", { value: platform, configurable: true }); +}; + +let binDir: string; + +const addBinary = (name: string) => { + const binaryPath = path.join(binDir, name); + fs.writeFileSync(binaryPath, "#!/bin/sh\n"); + fs.chmodSync(binaryPath, 0o755); + return binaryPath; +}; + +const expectOsascript = (message: string, title: string) => { + expect(mockSpawn).toHaveBeenCalledWith( + "/usr/bin/osascript", + ["-e", `display notification "${message}" with title "${title}"`], + { stdio: "ignore", detached: true }, + ); +}; + +describe("desktop notifications", () => { + beforeEach(() => { + mockSpawn.mockReset(); + errorHandlers.length = 0; + mockSpawn.mockImplementation(() => { + const child = { + on: jest.fn((event: string, handler: (error: Error) => void) => { + if (event === "error") errorHandlers.push(handler); + return child; + }), + unref: jest.fn(), + }; + return child; + }); + + binDir = fs.mkdtempSync(path.join(os.tmpdir(), "n-apt-notify-")); + process.env.PATH = binDir; + }); + + afterEach(() => { + setPlatform(originalPlatform); + process.env.PATH = originalPath; + fs.rmSync(binDir, { recursive: true, force: true }); + }); + + it("escapes AppleScript string literals", () => { + const { toAppleScriptString } = requireNotify(); + + expect(toAppleScriptString('say "hi"')).toBe('"say \\"hi\\""'); + expect(toAppleScriptString("C:\\path")).toBe('"C:\\\\path"'); + }); + + it("prefers a native terminal-notifier on PATH, keeping icon and open", () => { + setPlatform("darwin"); + const binary = addBinary("terminal-notifier"); + + const { notify } = requireNotify(); + notify({ + title: "N-APT", + message: "✓ Build done", + icon: "/tmp/icon.png", + open: "http://localhost:5173", + }); + + expect(mockSpawn).toHaveBeenCalledTimes(1); + expect(mockSpawn).toHaveBeenCalledWith( + binary, + [ + "-title", + "N-APT", + "-message", + "✓ Build done", + "-appIcon", + "/tmp/icon.png", + "-open", + "http://localhost:5173", + ], + { stdio: "ignore", detached: true }, + ); + }); + + it("falls back to osascript when no terminal-notifier is installed", () => { + setPlatform("darwin"); + + const { notify } = requireNotify(); + notify({ title: "N-APT", message: "✓ Build done", open: "http://localhost:5173" }); + + expect(mockSpawn).toHaveBeenCalledTimes(1); + expectOsascript("✓ Build done", "N-APT"); + }); + + it("falls back to osascript when terminal-notifier cannot launch", () => { + setPlatform("darwin"); + const binary = addBinary("terminal-notifier"); + + const { notify } = requireNotify(); + notify({ title: "N-APT", message: "✓ Build done" }); + + expect(mockSpawn).toHaveBeenCalledTimes(1); + expect(mockSpawn.mock.calls[0][0]).toBe(binary); + + errorHandlers.forEach(handler => { + handler(new Error("spawn Unknown system error -86")); + }); + + expect(mockSpawn).toHaveBeenCalledTimes(2); + expect(mockSpawn.mock.calls[1][0]).toBe("/usr/bin/osascript"); + + errorHandlers.length = 0; + notify({ title: "N-APT", message: "✓ And again" }); + + expect(mockSpawn).toHaveBeenCalledTimes(3); + expect(mockSpawn.mock.calls[2][0]).toBe("/usr/bin/osascript"); + }); + + it("falls back to osascript when terminal-notifier throws synchronously", () => { + setPlatform("darwin"); + addBinary("terminal-notifier"); + + const error = new Error("spawn Unknown system error -86") as NodeJS.ErrnoException; + error.code = "Unknown system error -86"; + mockSpawn.mockImplementationOnce(() => { + throw error; + }); + + const { notify } = requireNotify(); + expect(() => notify({ title: "N-APT", message: "✓ Build done" })).not.toThrow(); + + expect(mockSpawn).toHaveBeenCalledTimes(2); + expect(mockSpawn.mock.calls[1][0]).toBe("/usr/bin/osascript"); + }); + + it("uses notify-send on Linux when available", () => { + setPlatform("linux"); + const binary = addBinary("notify-send"); + + const { notify } = requireNotify(); + notify({ title: "N-APT", message: "✓ Build done", icon: "/tmp/icon.png" }); + + expect(mockSpawn).toHaveBeenCalledTimes(1); + expect(mockSpawn).toHaveBeenCalledWith( + binary, + ["-i", "/tmp/icon.png", "N-APT", "✓ Build done"], + { stdio: "ignore", detached: true }, + ); + }); + + it("stays silent on Linux without notify-send", () => { + setPlatform("linux"); + + const { notify } = requireNotify(); + expect(() => notify({ title: "N-APT", message: "✓ Build done" })).not.toThrow(); + expect(mockSpawn).not.toHaveBeenCalled(); + }); + + it("stays silent on platforms with no supported backend", () => { + setPlatform("win32"); + + const { notify } = requireNotify(); + expect(() => notify({ title: "N-APT", message: "✓ Build done" })).not.toThrow(); + expect(mockSpawn).not.toHaveBeenCalled(); + }); + + it("tolerates an empty PATH", () => { + setPlatform("darwin"); + process.env.PATH = ""; + + const { notify } = requireNotify(); + notify({ title: "N-APT", message: "✓ Build done" }); + + expectOsascript("✓ Build done", "N-APT"); + }); + + it("never propagates a synchronous spawn failure off macOS", () => { + setPlatform("linux"); + addBinary("notify-send"); + mockSpawn.mockImplementation(() => { + throw new Error("spawn EACCES"); + }); + + const { notify } = requireNotify(); + expect(() => notify({ title: "N-APT", message: "✓ Build done" })).not.toThrow(); + }); +}); diff --git a/test/ts/iqIntegrity.test.ts b/test/ts/iqIntegrity.test.ts new file mode 100644 index 00000000..cfdcf965 --- /dev/null +++ b/test/ts/iqIntegrity.test.ts @@ -0,0 +1,28 @@ +import { + integrityPlaceholder, + stampIntegrity, + verifyStampedIntegrity, +} from "@n-apt/webusb/iqIntegrity"; + +describe("NAPT v5 integrity stamping", () => { + it("stamps and verifies the complete placeholder-form file", async () => { + const bytes = new TextEncoder().encode( + `header trailer {"digest":"${integrityPlaceholder()}"}`, + ); + const stamped = await stampIntegrity(bytes); + const digest = new TextDecoder().decode(stamped).match(/[0-9a-f]{64}/)?.[0]; + + expect(digest).toMatch(/^[0-9a-f]{64}$/); + await expect(verifyStampedIntegrity(stamped, digest!)).resolves.toBe(true); + }); + + it("rejects a file changed after stamping", async () => { + const stamped = await stampIntegrity( + new TextEncoder().encode(`x ${integrityPlaceholder()} y`), + ); + const digest = new TextDecoder().decode(stamped).match(/[0-9a-f]{64}/)?.[0]; + stamped[0] ^= 1; + + await expect(verifyStampedIntegrity(stamped, digest!)).resolves.toBe(false); + }); +}); diff --git a/test/ts/runtimePaths.test.ts b/test/ts/runtimePaths.test.ts new file mode 100644 index 00000000..6e39a11a --- /dev/null +++ b/test/ts/runtimePaths.test.ts @@ -0,0 +1,19 @@ +import path from "node:path"; + +import { + appRuntimeDirectory, + backendHandoffLockPath, + backendTargetPath, + buildOrchestratorLockPath, + rebuildStatusPath, +} from "../../scripts/build/runtimePaths"; + +describe("app runtime paths", () => { + it("scopes build and backend handoff state under .n-apt", () => { + expect(appRuntimeDirectory).toBe(path.resolve(".n-apt")); + expect(backendHandoffLockPath).toBe(path.join(appRuntimeDirectory, "backend-handoff.lock")); + expect(backendTargetPath).toBe(path.join(appRuntimeDirectory, "backend-target.json")); + expect(buildOrchestratorLockPath).toBe(path.join(appRuntimeDirectory, "build-orchestrator.lock")); + expect(rebuildStatusPath).toBe(path.join(appRuntimeDirectory, "rebuild_status.json")); + }); +}); diff --git a/test/ts/scripts/rustHotReloadGate.test.ts b/test/ts/scripts/rustHotReloadGate.test.ts index 47fd25fe..924d90ff 100644 --- a/test/ts/scripts/rustHotReloadGate.test.ts +++ b/test/ts/scripts/rustHotReloadGate.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it, jest } from "@jest/globals"; import { - buildRustBackendStopCommand, canKeepRustHotReloadWatcherAttached, createRustHotReloadGate, getRustHotReloadProcessLabel, @@ -180,13 +179,6 @@ describe("Rust hot reload gate", () => { expect(result.stage).toBe("build_failed"); }); - it("builds a targeted rust backend stop command for hot reload", () => { - expect(buildRustBackendStopCommand(38510, "darwin")).toContain("38510"); - expect(buildRustBackendStopCommand(38510, "win32")).toContain("38510"); - expect(buildRustBackendStopCommand(38510, "darwin")).not.toContain("pkill"); - expect(buildRustBackendStopCommand(38510, "win32")).not.toContain("pkill"); - }); - it("formats hot-reload process and runtime labels separately", () => { expect(getRustHotReloadProcessLabel("running", "Checking Rust backend...")) .toBe("[HOT-RELOAD] Rebuilding Rust backend..."); diff --git a/test/ts/scripts/rustHotReloadHandoff.test.ts b/test/ts/scripts/rustHotReloadHandoff.test.ts new file mode 100644 index 00000000..b6a93df3 --- /dev/null +++ b/test/ts/scripts/rustHotReloadHandoff.test.ts @@ -0,0 +1,307 @@ +import { describe, expect, it } from "@jest/globals"; +import { + evaluateSdrReadiness, + extractDeviceState, + extractSourceKinds, + formatSdrReadyFailure, + isHardwareSourceKind, + waitForProcessExit, + waitForSdrReady, + type FetchLike, +} from "../../../scripts/build/rustHotReloadHandoff"; + +const deviceConnected = { + device: { connected: true, state: "connected", info: "HackRF One" }, +}; +const deviceMock = { + device: { connected: false, state: "disconnected", info: "Mock APT SDR" }, +}; +const snapshotWithoutRadio = { + status: { sources: [{ id: "mock-apt", kind: "mock_apt" }] }, +}; +const snapshotWithHackRf = { + status: { + sources: [ + { id: "hackrf_one-abc", kind: "hackrf_one" }, + { id: "mock-apt", kind: "mock_apt" }, + ], + }, +}; + +/** A virtual clock driven by the injected sleep, so polls are deterministic. */ +function createHarness(options: { + device?: unknown; + snapshot?: unknown; + fetchImpl?: FetchLike; +}) { + const clock = { value: 0 }; + const fetchImpl: FetchLike = + options.fetchImpl ?? + (async (url: string) => { + const path = new URL(url).pathname; + const body = path === "/api/agent/status" ? options.device : options.snapshot; + if (body === undefined) return { ok: false, json: async () => null }; + return { ok: true, json: async () => body }; + }); + + return { + clock, + fetchImpl, + now: () => clock.value, + sleep: async (ms: number) => { + clock.value += ms; + }, + }; +} + +describe("Rust hot reload handoff", () => { + it("distinguishes exclusive hardware sources from simulations", () => { + expect(isHardwareSourceKind("hackrf_one")).toBe(true); + expect(isHardwareSourceKind("rtl-sdr")).toBe(true); + expect(isHardwareSourceKind("mock_apt")).toBe(false); + expect(isHardwareSourceKind("mock_tx")).toBe(false); + }); + + it("reads source kinds from the backend snapshot", () => { + expect(extractSourceKinds(snapshotWithHackRf)).toEqual([ + "hackrf_one", + "mock_apt", + ]); + expect(extractSourceKinds({ status: { sources: [{ id: "x" }, null] } })).toEqual( + [], + ); + expect(extractSourceKinds({ status: {} })).toEqual([]); + expect(extractSourceKinds(null)).toEqual([]); + }); + + it("reads device state from the agent status payload only", () => { + expect(extractDeviceState(deviceConnected)).toEqual({ + connected: true, + state: "connected", + info: "HackRF One", + }); + // The flat `/status` route carries no device block. + expect(extractDeviceState(snapshotWithHackRf)).toBeNull(); + // A device block without a boolean connection flag is not usable. + expect(extractDeviceState({ device: { state: "connected" } })).toBeNull(); + expect(extractDeviceState(null)).toBeNull(); + }); + + it("treats a reported connection as ready", () => { + expect(evaluateSdrReadiness(deviceConnected.device, ["hackrf_one"])).toEqual({ + ready: true, + outcome: "connected", + }); + }); + + it("allows the reload when no supported radio is attached", () => { + expect(evaluateSdrReadiness(deviceMock.device, ["mock_apt"])).toEqual({ + ready: true, + outcome: "no-hardware", + }); + expect(evaluateSdrReadiness(null, [])).toEqual({ + ready: true, + outcome: "no-hardware", + }); + }); + + it("keeps waiting while an attached radio has not connected", () => { + expect(evaluateSdrReadiness(deviceMock.device, ["hackrf_one"])).toEqual({ + ready: false, + outcome: "waiting", + }); + }); + + it("names the radio that failed to connect", () => { + const message = formatSdrReadyFailure({ + ok: false, + outcome: "failed", + device: { connected: false, state: "disconnected", info: "Mock APT SDR" }, + hardwareSourceKinds: ["hackrf_one", "mock_apt"], + }); + + expect(message).toContain("hackrf_one"); + expect(message).toContain("state=disconnected"); + expect(message).not.toContain("mock_apt"); + }); + + it("resolves as soon as the replacement backend reports a connection", async () => { + const harness = createHarness({ + device: deviceConnected, + snapshot: snapshotWithHackRf, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(true); + expect(result.outcome).toBe("connected"); + expect(result.device?.info).toBe("HackRF One"); + expect(harness.clock.value).toBe(0); + }); + + it("accepts an absent radio once the inventory has settled", async () => { + const harness = createHarness({ + device: deviceMock, + snapshot: snapshotWithoutRadio, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + settleMs: 500, + pollMs: 100, + timeoutMs: 5000, + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(true); + expect(result.outcome).toBe("no-hardware"); + expect(harness.clock.value).toBeGreaterThanOrEqual(500); + }); + + it("does not trust an empty inventory before the settle window elapses", async () => { + const harness = createHarness({ + device: deviceMock, + snapshot: snapshotWithoutRadio, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + settleMs: 1500, + pollMs: 250, + timeoutMs: 800, + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(false); + expect(result.outcome).toBe("failed"); + }); + + it("fails when an attached radio never connects", async () => { + const harness = createHarness({ + device: deviceMock, + snapshot: snapshotWithHackRf, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + settleMs: 0, + pollMs: 250, + timeoutMs: 1000, + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(false); + expect(result.outcome).toBe("failed"); + expect(result.hardwareSourceKinds).toContain("hackrf_one"); + expect(formatSdrReadyFailure(result)).toContain("hackrf_one"); + }); + + it("does not mistake an unreachable backend for a machine with no radio", async () => { + const harness = createHarness({ + fetchImpl: async () => { + throw new Error("ECONNREFUSED"); + }, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + settleMs: 0, + pollMs: 250, + timeoutMs: 1000, + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(false); + expect(result.outcome).toBe("failed"); + }); + + it("remembers a radio enumerated before the inventory went empty", async () => { + let snapshotPolls = 0; + const harness = createHarness({ + fetchImpl: async (url: string) => { + const path = new URL(url).pathname; + if (path === "/api/agent/status") { + return { ok: true, json: async () => deviceMock }; + } + snapshotPolls += 1; + return { + ok: true, + json: async () => + snapshotPolls === 1 ? snapshotWithHackRf : snapshotWithoutRadio, + }; + }, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + settleMs: 0, + pollMs: 250, + timeoutMs: 1000, + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + }); + + expect(result.ok).toBe(false); + expect(result.outcome).toBe("failed"); + expect(result.hardwareSourceKinds).toContain("hackrf_one"); + }); + + it("reports cancellation without claiming a handoff", async () => { + const harness = createHarness({ + device: deviceMock, + snapshot: snapshotWithHackRf, + }); + + const result = await waitForSdrReady({ + baseUrl: "http://127.0.0.1:9000", + fetchImpl: harness.fetchImpl, + sleep: harness.sleep, + now: harness.now, + isCancelled: () => true, + }); + + expect(result.ok).toBe(false); + expect(result.outcome).toBe("cancelled"); + }); + + it("waits for a pid to disappear and gives up when it outlives the timeout", async () => { + const noSleep = async () => {}; + + const exited = await waitForProcessExit({ + pid: 4242, + timeoutMs: 1000, + pollMs: 100, + isAlive: () => false, + sleep: noSleep, + }); + expect(exited).toBe(true); + + // A process that only disappears after the deadline is not awaited again. + let aliveChecks = 0; + const stubborn = await waitForProcessExit({ + pid: 4242, + timeoutMs: 0, + isAlive: () => { + aliveChecks += 1; + return true; + }, + sleep: noSleep, + }); + expect(stubborn).toBe(false); + expect(aliveChecks).toBeGreaterThan(0); + }); +}); diff --git a/test/ts/sourceModeManagement.test.ts b/test/ts/sourceModeManagement.test.ts index 1638579d..b6b54c8e 100644 --- a/test/ts/sourceModeManagement.test.ts +++ b/test/ts/sourceModeManagement.test.ts @@ -181,7 +181,7 @@ describe("sourceModeManagement", () => { }); }); - it("returns a deterministic Rx handoff that clears Tx and requests an Rx frame", () => { + it("holds Rx paused when a half-duplex device leaves Tx", () => { expect( resolveSourceModeTransition({ sourceId: "hackrf-1", @@ -195,13 +195,47 @@ describe("sourceModeManagement", () => { toMode: "rx", actions: [ "clear_tx_binding", - "resume_rx", + "pause_rx", "request_rx_mode", "request_rx_frame", ], }); }); + it("never pauses Rx when leaving Tx on a source that kept it streaming", () => { + for (const duplexMode of ["duplex", "simplex"] as const) { + expect( + resolveSourceModeTransition({ + sourceId: "device-1", + duplexMode, + fromMode: "tx", + toMode: "rx", + }), + ).toEqual({ + sourceId: "device-1", + fromMode: "tx", + toMode: "rx", + actions: ["clear_tx_binding", "request_rx_mode", "request_rx_frame"], + }); + } + }); + + it("reports no actions when the mode does not change", () => { + expect( + resolveSourceModeTransition({ + sourceId: "hackrf-1", + duplexMode: "half_duplex", + fromMode: "rx", + toMode: "rx", + }), + ).toEqual({ + sourceId: "hackrf-1", + fromMode: "rx", + toMode: "rx", + actions: [], + }); + }); + it("enters Tx standby without treating Tx as paused", () => { expect( resolveSourceModeTransition({ diff --git a/test/ts/useLiveSampleRateControl.test.tsx b/test/ts/useLiveSampleRateControl.test.tsx index 5c7cb2f7..82e12009 100644 --- a/test/ts/useLiveSampleRateControl.test.tsx +++ b/test/ts/useLiveSampleRateControl.test.tsx @@ -178,7 +178,7 @@ describe("useLiveSampleRateControl", () => { }); }); - it("repairs Whole Channel edges even when a stale range has the same span", () => { + it("keeps a same-span acquisition window instead of snapping back to the channel edges", () => { const applyFrequencyRange = jest.fn(); const initialProps = { sourceMode: "live" as const, @@ -204,6 +204,9 @@ describe("useLiveSampleRateControl", () => { }); applyFrequencyRange.mockClear(); + // A scrolled window that keeps its span equal to the sample rate is a valid + // acquisition window. Re-anchoring it to the channel edges here is what made + // the viewport elastically snap back while browsing the spectrum. rerender({ ...initialProps, activeChannelSampleRate: 18_250_000, @@ -212,10 +215,7 @@ describe("useLiveSampleRateControl", () => { sampleRateHz: 18_250_000, }); - expect(applyFrequencyRange).toHaveBeenCalledWith({ - min: 4_750_000, - max: 23_000_000, - }); + expect(applyFrequencyRange).not.toHaveBeenCalled(); }); it("notifies dependent controls whenever the sample rate is applied", () => { @@ -532,7 +532,10 @@ describe("useLiveSampleRateControl", () => { result.current.handleSampleRateChange(4_372_000); }); expect(setSampleRate).toHaveBeenLastCalledWith(4_372_000); - expect(applyFrequencyRange).toHaveBeenLastCalledWith({ + // The explicit whole-channel selection still applies the channel viewport. + // A span reconciliation may publish afterwards (the prop range lags in this + // harness), so assert the call happened rather than that it was last. + expect(applyFrequencyRange).toHaveBeenCalledWith({ min: 18_000, max: 4_390_000, }); diff --git a/test/ts/useSdrSettings.test.tsx b/test/ts/useSdrSettings.test.tsx index 06de7dac..bf76eff7 100644 --- a/test/ts/useSdrSettings.test.tsx +++ b/test/ts/useSdrSettings.test.tsx @@ -538,7 +538,7 @@ describe("useSdrSettings", () => { localStorage.removeItem("napt-spectrum-view-v1:hackrf_one"); }); - it("does not re-enable HackRF baseband filtering when sample rate changes while disabled", () => { + it("follows the sample rate instead of publishing a zero baseband width", () => { const store = configureStore({ reducer: { spectrum: spectrumSlice, @@ -554,6 +554,8 @@ describe("useSdrSettings", () => { hackrfLnaGain: 0, hackrfVgaGain: 30, hackrfAmpEnabled: false, + // A stale zero (the removed "disabled" state) must not survive: the + // filter has no off state, so it mirrors the sample rate unless pinned. hackrfBasebandBandwidth: 0, ppm: 2, tunerAGC: false, @@ -588,13 +590,136 @@ describe("useSdrSettings", () => { hookApi!.setSampleRate(5_200_000); }); - expect(store.getState().spectrum.hackrfBasebandBandwidth).toBe(0); + expect(store.getState().spectrum.hackrfBasebandBandwidth).toBe(5_200_000); expect(mockOnSettingsChange).toHaveBeenLastCalledWith( expect.objectContaining({ sampleRate: 5_200_000, - tunerBandwidth: 0, + tunerBandwidth: 5_200_000, + }), + ); + expect(mockOnSettingsChange).not.toHaveBeenCalledWith( + expect.objectContaining({ tunerBandwidth: 0 }), + ); + }); + + it("holds the disabled baseband sentinel out of the device payload", () => { + const store = configureStore({ + reducer: { + spectrum: spectrumSlice, + }, + }); + + store.dispatch( + setSdrSettingsBundle({ + fftSize: 16384, + fftWindow: "Rectangular", + fftFrameRate: 42, + gain: 49.6, + hackrfLnaGain: 0, + hackrfVgaGain: 30, + hackrfAmpEnabled: false, + hackrfBasebandBandwidth: 3_200_000, + ppm: 2, + tunerAGC: false, + rtlAGC: true, + sampleRateHz: 3_200_000, + }), + ); + + let hookApi: ReturnType | null = null; + + const Harness = () => { + hookApi = useSdrSettings({ + maxSampleRate: 20_000_000, + currentSampleRateHz: 3_200_000, + deviceType: "hackrf_one", + onSettingsChange: mockOnSettingsChange as any, + sdrSettings: mockSdrSettings, + spectrumStateOverride: store.getState().spectrum as any, + }); + return null; + }; + + render( + + + , + ); + + mockOnSettingsChange.mockClear(); + + act(() => { + hookApi!.setHackrfBasebandBandwidth(0); + }); + + // The sentinel is held in state so the control can render the filter off... + expect(store.getState().spectrum.hackrfBasebandBandwidth).toBe(0); + // ...but a zero width is never published to the hardware, where it is not a + // valid MAX2837 filter and would fail the filter set. + expect(mockOnSettingsChange).not.toHaveBeenCalledWith( + expect.objectContaining({ tunerBandwidth: 0 }), + ); + }); + + it("does not re-enable a disabled baseband filter when the sample rate changes", () => { + const store = configureStore({ + reducer: { + spectrum: spectrumSlice, + }, + }); + + store.dispatch( + setSdrSettingsBundle({ + fftSize: 16384, + fftWindow: "Rectangular", + fftFrameRate: 42, + gain: 49.6, + hackrfLnaGain: 0, + hackrfVgaGain: 30, + hackrfAmpEnabled: false, + // Off is a held, pinned state: the filter must stay disabled. + hackrfBasebandBandwidth: 0, + basebandFilterPinned: true, + ppm: 2, + tunerAGC: false, + rtlAGC: true, + sampleRateHz: 3_200_000, }), ); + + let hookApi: ReturnType | null = null; + + const Harness = () => { + hookApi = useSdrSettings({ + maxSampleRate: 20_000_000, + currentSampleRateHz: 3_200_000, + deviceType: "hackrf_one", + onSettingsChange: mockOnSettingsChange as any, + sdrSettings: mockSdrSettings, + spectrumStateOverride: store.getState().spectrum as any, + }); + return null; + }; + + render( + + + , + ); + + mockOnSettingsChange.mockClear(); + + act(() => { + hookApi!.setSampleRate(5_200_000); + }); + + expect(store.getState().spectrum.hackrfBasebandBandwidth).toBe(0); + expect(mockOnSettingsChange).toHaveBeenLastCalledWith( + expect.objectContaining({ sampleRate: 5_200_000 }), + ); + expect(mockOnSettingsChange).not.toHaveBeenCalledWith( + expect.objectContaining({ tunerBandwidth: 0 }), + ); }); it("keeps a pinned HackRF baseband filter fixed when the sample rate changes", () => { diff --git a/test/ts/useSpectrumInteraction.test.tsx b/test/ts/useSpectrumInteraction.test.tsx index aaebba35..e68aa022 100644 --- a/test/ts/useSpectrumInteraction.test.tsx +++ b/test/ts/useSpectrumInteraction.test.tsx @@ -1321,6 +1321,25 @@ describe("useSpectrumInteraction Hook", () => { }); }); + it("does not trap zoomed wheel panning inside the active channel without hardware bounds", () => { + renderHook(() => + useSpectrumInteraction({ + ...defaultOptions, + frequencyRangeRef: { current: { min: 0, max: 100 } }, + signalAreaBounds: { TEST: { min: 40, max: 60 } }, + hardwareSpectrumBounds: null, + vizZoomRef: { current: 2 }, + vizPanOffsetRef: { current: 0 }, + }), + ); + + // A wheel outside the VFO rows pans the viewport. The channel [40, 60] + // must not cap the pan: the acquisition window [0, 100] is the bound. + triggerWheel({ clientX: 500, clientY: 10, deltaY: 200 }); + + expect(mockOnVizPanChange).toHaveBeenLastCalledWith(10); + }); + it("allows Whole Channel dragging below the selected channel start", () => { const localRangeRef = { current: { min: 4_750_000, max: 23_000_000 } }; diff --git a/test/ts/useSpectrumStore.sampleRate.test.ts b/test/ts/useSpectrumStore.sampleRate.test.ts index 2e8b4f84..1c36d9bc 100644 --- a/test/ts/useSpectrumStore.sampleRate.test.ts +++ b/test/ts/useSpectrumStore.sampleRate.test.ts @@ -134,14 +134,20 @@ describe("resolveSourceSwitchDisplaySettings", () => { }); describe("resolveLiveAcquisitionBounds", () => { - it("prefers the device bounds over a subscriber-local channel frame", () => { + it("uses the hydrated device bounds", () => { expect( resolveLiveAcquisitionBounds({ hardwareBounds: { min: 0, max: 30_000_000_000 }, - channelBounds: { min: 6_780, max: 4_390_000 }, }), ).toEqual({ min: 0, max: 30_000_000_000 }); }); + + it("falls back to the global spectrum bounds instead of a channel frame", () => { + expect(resolveLiveAcquisitionBounds({ hardwareBounds: null })).toEqual({ + min: 0, + max: 30_000_000_000, + }); + }); }); describe("resolveInitialSourceHydrationSettings", () => { diff --git a/test/ts/useWebGPULifecycle.overlayReadiness.test.tsx b/test/ts/useWebGPULifecycle.overlayReadiness.test.tsx new file mode 100644 index 00000000..02029edd --- /dev/null +++ b/test/ts/useWebGPULifecycle.overlayReadiness.test.tsx @@ -0,0 +1,100 @@ +/** @jest-environment jsdom */ +import { renderHook, waitFor } from "@testing-library/react"; +import { useWebGPULifecycle } from "@n-apt/spectrum/hooks/useWebGPUInit"; + +// A never-settling `lost` promise keeps the device alive. The shared device +// pool in the canvas shim resolves `lost` immediately, which would tear the +// canvas back down part-way through the test. +jest.mock("@n-apt/app/infrastructure/visualization/webgpuDevicePool", () => { + const device = { + createSampler: jest.fn(() => ({})), + createBindGroupLayout: jest.fn(() => ({})), + createShaderModule: jest.fn(() => ({})), + createRenderPipeline: jest.fn(() => ({})), + createComputePipeline: jest.fn(() => ({})), + createPipelineLayout: jest.fn(() => ({})), + createBuffer: jest.fn(() => ({ destroy: jest.fn() })), + createTexture: jest.fn(() => ({ + createView: jest.fn(() => ({})), + destroy: jest.fn(), + })), + createBindGroup: jest.fn(() => ({})), + queue: { + writeBuffer: jest.fn(), + copyExternalImageToTexture: jest.fn(), + submit: jest.fn(), + }, + lost: new Promise(() => {}), + }; + + return { + acquireSharedWebGpuDevice: jest.fn( + async () => device as unknown as GPUDevice, + ), + resetSharedWebGpuDeviceForTests: jest.fn(), + }; +}); + +class FakeOffscreenCanvas { + width = 1; + height = 1; + getContext() { + return { clearRect: jest.fn(), setTransform: jest.fn() }; + } +} + +describe("useWebGPULifecycle overlay readiness", () => { + beforeAll(() => { + // jsdom has neither OffscreenCanvas nor the WebGPU enum objects the + // overlay renderer builds its bind group layout from. + const globals = globalThis as unknown as Record; + globals.OffscreenCanvas = FakeOffscreenCanvas; + globals.GPUShaderStage = { FRAGMENT: 0x2 }; + globals.GPUTextureUsage = { + TEXTURE_BINDING: 0x4, + COPY_DST: 0x2, + RENDER_ATTACHMENT: 0x10, + }; + }); + + it("has the grid overlay renderer ready before the canvas can paint", async () => { + // The grid, axes and labels are composed from the grid overlay texture in + // the same pass as the spectrum. If the renderer is built after the paint + // gate opens, the first painted frame has no grid and the chrome pops in a + // frame later — the preview-frame flash in file playback. + const renders: Array<{ + enabled: boolean; + initializing: boolean; + gridReady: boolean; + }> = []; + + const initOptions: Parameters[0] = { + spectrumGpuCanvasRef: { current: null }, + waterfallGpuCanvasRef: { current: null }, + resampleWgsl: "// resample", + resampleComputePipelineRef: { current: null }, + resampleParamsBufferRef: { current: null }, + gpuBufferPoolRef: { current: [] }, + }; + + const { result } = renderHook(() => { + const lifecycle = useWebGPULifecycle(initOptions); + + renders.push({ + enabled: lifecycle.webgpuEnabled, + initializing: lifecycle.isInitializingWebGPU, + gridReady: !!lifecycle.gridOverlayRendererRef.current, + }); + + return lifecycle; + }); + + await waitFor(() => expect(result.current.webgpuEnabled).toBe(true)); + + const paintableRenders = renders.filter( + (render) => render.enabled && !render.initializing, + ); + expect(paintableRenders.length).toBeGreaterThan(0); + expect(paintableRenders.every((render) => render.gridReady)).toBe(true); + }); +}); diff --git a/test/wasm/Cargo.toml b/test/wasm/Cargo.toml index 7ae9b057..688a24a2 100644 --- a/test/wasm/Cargo.toml +++ b/test/wasm/Cargo.toml @@ -8,15 +8,15 @@ repository = "https://github.com/ceane/n-apt" crate-type = ["cdylib"] [dependencies.web-sys] -version = "0.3.97" +version = "0.3.105" features = ["console"] [dependencies] -wasm-bindgen = "0.2.120" -wasm-bindgen-test = "0.3.70" -js-sys = "0.3.97" +wasm-bindgen = "0.2.128" +wasm-bindgen-test = "0.3.78" +js-sys = "0.3.105" n-apt-backend = { path = "../../" } -getrandom = { version = "0.2", features = ["js"] } +getrandom = { version = "0.4", features = ["wasm_js"] } serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.6" diff --git a/tsconfig.json b/tsconfig.json index 5e828705..f2fef3d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -93,7 +93,7 @@ "@n-apt/encrypted-modules/*": ["./src/encrypted-modules/*"], } }, - "include": ["src/ts", "src/app-article", "src/app-game", "src/app-legal", ".react-router/types/**/*", "src/ts/types", "src/types", "test/ts", "test/integration"], + "include": ["src/ts", "src/app-article", "src/app-game", "src/app-legal", ".react-router/types/**/*", "src/ts/types", "src/types", "test/ts", "test/integration", "scripts"], "exclude": ["src/encrypted-modules", "src/app-article/components/canvas/ascii-motion-animation.tsx"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/vendor/block/Cargo.toml b/vendor/block/Cargo.toml new file mode 100644 index 00000000..380432dd --- /dev/null +++ b/vendor/block/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "block" +version = "0.1.6" +authors = ["Steven Sheldon"] + +description = "Rust interface for Apple's C language extension of blocks." +keywords = ["blocks", "osx", "ios", "objective-c"] +readme = "README.md" +repository = "http://github.com/SSheldon/rust-block" +documentation = "http://ssheldon.github.io/rust-objc/block/" +license = "MIT" + +exclude = [ + ".gitignore", + ".travis.yml", + "travis_install.sh", + "travis_test.sh", + "tests-ios/**", +] + +# This vendored copy builds as a path dependency, so cargo no longer applies the +# `--cap-lints allow` it uses for registry crates and upstream's bare `extern` +# declarations would surface as `missing_abi` warnings. Keep them quiet rather than +# touching more of the upstream source, so the patch stays a minimal diff. +[lints.rust] +missing_abi = "allow" diff --git a/vendor/block/README.md b/vendor/block/README.md new file mode 100644 index 00000000..78c65d08 --- /dev/null +++ b/vendor/block/README.md @@ -0,0 +1,42 @@ +Rust interface for Apple's C language extension of blocks. + +For more information on the specifics of the block implementation, see +Clang's documentation: http://clang.llvm.org/docs/Block-ABI-Apple.html + +## Invoking blocks + +The `Block` struct is used for invoking blocks from Objective-C. For example, +consider this Objective-C function: + +``` objc +int32_t sum(int32_t (^block)(int32_t, int32_t)) { + return block(5, 8); +} +``` + +We could write it in Rust as the following: + +``` rust +unsafe fn sum(block: &Block<(i32, i32), i32>) -> i32 { + block.call((5, 8)) +} +``` + +Note the extra parentheses in the `call` method, since the arguments must be +passed as a tuple. + +## Creating blocks + +Creating a block to pass to Objective-C can be done with the `ConcreteBlock` +struct. For example, to create a block that adds two `i32`s, we could write: + +``` rust +let block = ConcreteBlock::new(|a: i32, b: i32| a + b); +let block = block.copy(); +assert!(unsafe { block.call((5, 8)) } == 13); +``` + +It is important to copy your block to the heap (with the `copy` method) before +passing it to Objective-C; this is because our `ConcreteBlock` is only meant +to be copied once, and we can enforce this in Rust, but if Objective-C code +were to copy it twice we could have a double free. diff --git a/vendor/block/src/lib.rs b/vendor/block/src/lib.rs new file mode 100644 index 00000000..8e981485 --- /dev/null +++ b/vendor/block/src/lib.rs @@ -0,0 +1,403 @@ +/*! +A Rust interface for Objective-C blocks. + +For more information on the specifics of the block implementation, see +Clang's documentation: http://clang.llvm.org/docs/Block-ABI-Apple.html + +# Invoking blocks + +The `Block` struct is used for invoking blocks from Objective-C. For example, +consider this Objective-C function: + +``` objc +int32_t sum(int32_t (^block)(int32_t, int32_t)) { + return block(5, 8); +} +``` + +We could write it in Rust as the following: + +``` +# use block::Block; +unsafe fn sum(block: &Block<(i32, i32), i32>) -> i32 { + block.call((5, 8)) +} +``` + +Note the extra parentheses in the `call` method, since the arguments must be +passed as a tuple. + +# Creating blocks + +Creating a block to pass to Objective-C can be done with the `ConcreteBlock` +struct. For example, to create a block that adds two `i32`s, we could write: + +``` +# use block::ConcreteBlock; +let block = ConcreteBlock::new(|a: i32, b: i32| a + b); +let block = block.copy(); +assert!(unsafe { block.call((5, 8)) } == 13); +``` + +It is important to copy your block to the heap (with the `copy` method) before +passing it to Objective-C; this is because our `ConcreteBlock` is only meant +to be copied once, and we can enforce this in Rust, but if Objective-C code +were to copy it twice we could have a double free. +*/ + +#[cfg(test)] +mod test_utils; + +use std::marker::PhantomData; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::os::raw::{c_int, c_ulong, c_void}; +use std::ptr; + +// Local patch: upstream declares this as an empty `enum Class { }`, which makes +// `static _NSConcreteStackBlock: Class` an uninhabited static (deny-by-default +// `uninhabited_static`/`future_incompatible` lint). An opaque zero-sized struct +// keeps the same ABI use (address-only, never dereferenced) without the lint. +#[repr(C)] +struct Class { + _private: [usize; 0], +} + +#[cfg_attr(any(target_os = "macos", target_os = "ios"), + link(name = "System", kind = "dylib"))] +#[cfg_attr(not(any(target_os = "macos", target_os = "ios")), + link(name = "BlocksRuntime", kind = "dylib"))] +extern { + static _NSConcreteStackBlock: Class; + + fn _Block_copy(block: *const c_void) -> *mut c_void; + fn _Block_release(block: *const c_void); +} + +/// Types that may be used as the arguments to an Objective-C block. +pub trait BlockArguments: Sized { + /// Calls the given `Block` with self as the arguments. + /// + /// Unsafe because `block` must point to a valid `Block` and this invokes + /// foreign code whose safety the compiler cannot verify. + unsafe fn call_block(self, block: *mut Block) -> R; +} + +macro_rules! block_args_impl { + ($($a:ident : $t:ident),*) => ( + impl<$($t),*> BlockArguments for ($($t,)*) { + unsafe fn call_block(self, block: *mut Block) -> R { + let invoke: unsafe extern fn(*mut Block $(, $t)*) -> R = { + let base = block as *mut BlockBase; + mem::transmute((*base).invoke) + }; + let ($($a,)*) = self; + invoke(block $(, $a)*) + } + } + ); +} + +block_args_impl!(); +block_args_impl!(a: A); +block_args_impl!(a: A, b: B); +block_args_impl!(a: A, b: B, c: C); +block_args_impl!(a: A, b: B, c: C, d: D); +block_args_impl!(a: A, b: B, c: C, d: D, e: E); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); +block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L); + +#[repr(C)] +struct BlockBase { + isa: *const Class, + flags: c_int, + _reserved: c_int, + invoke: unsafe extern fn(*mut Block, ...) -> R, +} + +/// An Objective-C block that takes arguments of `A` when called and +/// returns a value of `R`. +#[repr(C)] +pub struct Block { + _base: PhantomData>, +} + +impl Block where A: BlockArguments { + /// Call self with the given arguments. + /// + /// Unsafe because this invokes foreign code that the caller must verify + /// doesn't violate any of Rust's safety rules. For example, if this block + /// is shared with multiple references, the caller must ensure that calling + /// it will not cause a data race. + pub unsafe fn call(&self, args: A) -> R { + args.call_block(self as *const _ as *mut _) + } +} + +/// A reference-counted Objective-C block. +pub struct RcBlock { + ptr: *mut Block, +} + +impl RcBlock { + /// Construct an `RcBlock` for the given block without copying it. + /// The caller must ensure the block has a +1 reference count. + /// + /// Unsafe because `ptr` must point to a valid `Block` and must have a +1 + /// reference count or it will be overreleased when the `RcBlock` is + /// dropped. + pub unsafe fn new(ptr: *mut Block) -> Self { + RcBlock { ptr: ptr } + } + + /// Constructs an `RcBlock` by copying the given block. + /// + /// Unsafe because `ptr` must point to a valid `Block`. + pub unsafe fn copy(ptr: *mut Block) -> Self { + let ptr = _Block_copy(ptr as *const c_void) as *mut Block; + RcBlock { ptr: ptr } + } +} + +impl Clone for RcBlock { + fn clone(&self) -> RcBlock { + unsafe { + RcBlock::copy(self.ptr) + } + } +} + +impl Deref for RcBlock { + type Target = Block; + + fn deref(&self) -> &Block { + unsafe { &*self.ptr } + } +} + +impl Drop for RcBlock { + fn drop(&mut self) { + unsafe { + _Block_release(self.ptr as *const c_void); + } + } +} + +/// Types that may be converted into a `ConcreteBlock`. +pub trait IntoConcreteBlock: Sized where A: BlockArguments { + /// The return type of the resulting `ConcreteBlock`. + type Ret; + + /// Consumes self to create a `ConcreteBlock`. + fn into_concrete_block(self) -> ConcreteBlock; +} + +macro_rules! concrete_block_impl { + ($f:ident) => ( + concrete_block_impl!($f,); + ); + ($f:ident, $($a:ident : $t:ident),*) => ( + impl<$($t,)* R, X> IntoConcreteBlock<($($t,)*)> for X + where X: Fn($($t,)*) -> R { + type Ret = R; + + fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> { + unsafe extern fn $f<$($t,)* R, X>( + block_ptr: *mut ConcreteBlock<($($t,)*), R, X> + $(, $a: $t)*) -> R + where X: Fn($($t,)*) -> R { + let block = &*block_ptr; + (block.closure)($($a),*) + } + + let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; + unsafe { + ConcreteBlock::with_invoke(mem::transmute(f), self) + } + } + } + ); +} + +concrete_block_impl!(concrete_block_invoke_args0); +concrete_block_impl!(concrete_block_invoke_args1, a: A); +concrete_block_impl!(concrete_block_invoke_args2, a: A, b: B); +concrete_block_impl!(concrete_block_invoke_args3, a: A, b: B, c: C); +concrete_block_impl!(concrete_block_invoke_args4, a: A, b: B, c: C, d: D); +concrete_block_impl!(concrete_block_invoke_args5, a: A, b: B, c: C, d: D, e: E); +concrete_block_impl!(concrete_block_invoke_args6, a: A, b: B, c: C, d: D, e: E, f: F); +concrete_block_impl!(concrete_block_invoke_args7, a: A, b: B, c: C, d: D, e: E, f: F, g: G); +concrete_block_impl!(concrete_block_invoke_args8, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); +concrete_block_impl!(concrete_block_invoke_args9, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); +concrete_block_impl!(concrete_block_invoke_args10, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); +concrete_block_impl!(concrete_block_invoke_args11, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); +concrete_block_impl!(concrete_block_invoke_args12, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L); + +/// An Objective-C block whose size is known at compile time and may be +/// constructed on the stack. +#[repr(C)] +pub struct ConcreteBlock { + base: BlockBase, + descriptor: Box>>, + closure: F, +} + +impl ConcreteBlock + where A: BlockArguments, F: IntoConcreteBlock { + /// Constructs a `ConcreteBlock` with the given closure. + /// When the block is called, it will return the value that results from + /// calling the closure. + pub fn new(closure: F) -> Self { + closure.into_concrete_block() + } +} + +impl ConcreteBlock { + /// Constructs a `ConcreteBlock` with the given invoke function and closure. + /// Unsafe because the caller must ensure the invoke function takes the + /// correct arguments. + unsafe fn with_invoke(invoke: unsafe extern fn(*mut Self, ...) -> R, + closure: F) -> Self { + ConcreteBlock { + base: BlockBase { + isa: &_NSConcreteStackBlock, + // 1 << 25 = BLOCK_HAS_COPY_DISPOSE + flags: 1 << 25, + _reserved: 0, + invoke: mem::transmute(invoke), + }, + descriptor: Box::new(BlockDescriptor::new()), + closure: closure, + } + } +} + +impl ConcreteBlock where F: 'static { + /// Copy self onto the heap as an `RcBlock`. + pub fn copy(self) -> RcBlock { + unsafe { + let mut block = self; + let copied = RcBlock::copy(&mut *block); + // At this point, our copy helper has been run so the block will + // be moved to the heap and we can forget the original block + // because the heap block will drop in our dispose helper. + mem::forget(block); + copied + } + } +} + +impl Clone for ConcreteBlock where F: Clone { + fn clone(&self) -> Self { + unsafe { + ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), + self.closure.clone()) + } + } +} + +impl Deref for ConcreteBlock { + type Target = Block; + + fn deref(&self) -> &Block { + unsafe { &*(&self.base as *const _ as *const Block) } + } +} + +impl DerefMut for ConcreteBlock { + fn deref_mut(&mut self) -> &mut Block { + unsafe { &mut *(&mut self.base as *mut _ as *mut Block) } + } +} + +unsafe extern fn block_context_dispose(block: &mut B) { + // Read the block onto the stack and let it drop + ptr::read(block); +} + +unsafe extern fn block_context_copy(_dst: &mut B, _src: &B) { + // The runtime memmoves the src block into the dst block, nothing to do +} + +#[repr(C)] +struct BlockDescriptor { + _reserved: c_ulong, + block_size: c_ulong, + copy_helper: unsafe extern fn(&mut B, &B), + dispose_helper: unsafe extern fn(&mut B), +} + +impl BlockDescriptor { + fn new() -> BlockDescriptor { + BlockDescriptor { + _reserved: 0, + block_size: mem::size_of::() as c_ulong, + copy_helper: block_context_copy::, + dispose_helper: block_context_dispose::, + } + } +} + +#[cfg(test)] +mod tests { + use test_utils::*; + use super::{ConcreteBlock, RcBlock}; + + #[test] + fn test_call_block() { + let block = get_int_block_with(13); + unsafe { + assert!(block.call(()) == 13); + } + } + + #[test] + fn test_call_block_args() { + let block = get_add_block_with(13); + unsafe { + assert!(block.call((2,)) == 15); + } + } + + #[test] + fn test_create_block() { + let block = ConcreteBlock::new(|| 13); + let result = invoke_int_block(&block); + assert!(result == 13); + } + + #[test] + fn test_create_block_args() { + let block = ConcreteBlock::new(|a: i32| a + 5); + let result = invoke_add_block(&block, 6); + assert!(result == 11); + } + + #[test] + fn test_concrete_block_copy() { + let s = "Hello!".to_string(); + let expected_len = s.len() as i32; + let block = ConcreteBlock::new(move || s.len() as i32); + assert!(invoke_int_block(&block) == expected_len); + + let copied = block.copy(); + assert!(invoke_int_block(&copied) == expected_len); + } + + #[test] + fn test_concrete_block_stack_copy() { + fn make_block() -> RcBlock<(), i32> { + let x = 7; + let block = ConcreteBlock::new(move || x); + block.copy() + } + + let block = make_block(); + assert!(invoke_int_block(&block) == 7); + } +} diff --git a/vendor/block/src/test_utils.rs b/vendor/block/src/test_utils.rs new file mode 100644 index 00000000..940a3d22 --- /dev/null +++ b/vendor/block/src/test_utils.rs @@ -0,0 +1,31 @@ +extern crate objc_test_utils; + +use {Block, RcBlock}; + +pub fn get_int_block_with(i: i32) -> RcBlock<(), i32> { + unsafe { + let ptr = objc_test_utils::get_int_block_with(i); + RcBlock::new(ptr as *mut _) + } +} + +pub fn get_add_block_with(i: i32) -> RcBlock<(i32,), i32> { + unsafe { + let ptr = objc_test_utils::get_add_block_with(i); + RcBlock::new(ptr as *mut _) + } +} + +pub fn invoke_int_block(block: &Block<(), i32>) -> i32 { + let ptr = block as *const _; + unsafe { + objc_test_utils::invoke_int_block(ptr as *mut _) + } +} + +pub fn invoke_add_block(block: &Block<(i32,), i32>, a: i32) -> i32 { + let ptr = block as *const _; + unsafe { + objc_test_utils::invoke_add_block(ptr as *mut _, a) + } +} diff --git a/vite.config.js b/vite.config.js index 41816aa1..5429c1bb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -129,7 +129,7 @@ const rebuildStatusPlugin = () => ({ res.setHeader("Content-Type", "application/json"); res.setHeader("Access-Control-Allow-Origin", "*"); - const statusFile = path.resolve(dirname, ".rebuild_status.json"); + const statusFile = path.resolve(dirname, ".n-apt", "rebuild_status.json"); if (fs.existsSync(statusFile)) { res.end(fs.readFileSync(statusFile)); } else {