diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 213e5c36bf8..92a52692ed1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 2 - - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter with: token: '' diff --git a/.release/desktop-candidate.json b/.release/desktop-candidate.json index 67979737a90..843ac0dd7d7 100644 --- a/.release/desktop-candidate.json +++ b/.release/desktop-candidate.json @@ -1,10 +1,10 @@ { "schema": 2, - "version": "0.5.7", - "base_sha": "74b913cff8512c015dc6f1a7473b253fa803f954", - "previous_tag": "desktop-v0.5.6", - "previous_base_sha": "78c87ae20e182fffdd99744d6c9ff99df82b159c", - "previous_merge_sha": "3855687e7699f1b169f179122b8bc92f433abd6d", - "tag": "desktop-v0.5.7", - "commit_count": 5 + "version": "0.5.8", + "base_sha": "6a17d035f79ad582ca3f4f3cdc38d376f2c4087f", + "previous_tag": "desktop-v0.5.7", + "previous_base_sha": "74b913cff8512c015dc6f1a7473b253fa803f954", + "previous_merge_sha": "13c9e900c84cac1e2c8eeb7551bd1510ecb544d3", + "tag": "desktop-v0.5.8", + "commit_count": 4 } diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1cd6103a8..688e90d0fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v0.5.8 + +### Desktop and shared changes + +- feat(desktop): unify add agent flows ([#5015](https://github.com/block/buzz/pull/5015)) ([`02f640bc4559c48ac0c2ec595ef34dd2c294b0db`](https://github.com/block/buzz/commit/02f640bc4559c48ac0c2ec595ef34dd2c294b0db)) +- fix(buzz-agent): budget summarizer reasoning separately so it cannot starve the handoff summary ([#5248](https://github.com/block/buzz/pull/5248)) ([`c7b663680a29a837dbd2fdde810f239f3d303025`](https://github.com/block/buzz/commit/c7b663680a29a837dbd2fdde810f239f3d303025)) + +### Other repository changes + +- Revert "fix(acp): reject unattended permission requests" ([#5323](https://github.com/block/buzz/pull/5323)) ([`6a17d035f79ad582ca3f4f3cdc38d376f2c4087f`](https://github.com/block/buzz/commit/6a17d035f79ad582ca3f4f3cdc38d376f2c4087f)) +- infra: bind development services to loopback ([#4871](https://github.com/block/buzz/pull/4871)) ([`65834d68d0d3441c4e628540d6d5c8b0a2e757c9`](https://github.com/block/buzz/commit/65834d68d0d3441c4e628540d6d5c8b0a2e757c9)) + +[Compare desktop-v0.5.7...desktop-v0.5.8](https://github.com/block/buzz/compare/desktop-v0.5.7...desktop-v0.5.8) + ## v0.5.7 ### Desktop and shared changes diff --git a/Cargo.lock b/Cargo.lock index b574e8ffa27..0e8df32b4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -128,14 +128,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "appattest" @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", @@ -1247,7 +1247,7 @@ dependencies = [ [[package]] name = "buzz-relay" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-compression", @@ -1630,9 +1630,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -1640,9 +1640,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -1652,14 +1652,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -1710,7 +1710,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2390,7 +2390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2453,6 +2453,37 @@ dependencies = [ "tokio", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "deltae" version = "0.3.2" @@ -2553,9 +2584,9 @@ checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" [[package]] name = "diffy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05264ab2aab4fb952fc4b0f3f6eff1ddfb4563064053a4ea174d91537584a769" +checksum = "10aec8f7f9393bd6a4f2762be0ceb012d3cbe2478987258cc9960de148561914" dependencies = [ "hashbrown 0.17.1", ] @@ -2601,7 +2632,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2839,7 +2870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3312,7 +3343,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result 0.4.1", ] @@ -4450,6 +4481,59 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jni" version = "0.21.1" @@ -6183,7 +6267,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8405,7 +8489,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8464,7 +8548,7 @@ dependencies = [ "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8760,7 +8844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8917,9 +9001,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" dependencies = [ "base64 0.22.1", "bs58", @@ -8927,6 +9011,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.14.0", + "jiff", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -8937,9 +9022,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.21.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -9308,7 +9393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10075,7 +10160,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10088,7 +10173,7 @@ dependencies = [ "parking_lot", "rustix 1.1.4", "signal-hook", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -10824,7 +10909,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -11424,7 +11509,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/benchmarks/harbor-buzz-orchestra/src/harbor_buzz_orchestra/container_runtime.py b/benchmarks/harbor-buzz-orchestra/src/harbor_buzz_orchestra/container_runtime.py index ed883a820aa..a0602111d13 100644 --- a/benchmarks/harbor-buzz-orchestra/src/harbor_buzz_orchestra/container_runtime.py +++ b/benchmarks/harbor-buzz-orchestra/src/harbor_buzz_orchestra/container_runtime.py @@ -24,7 +24,7 @@ from .provisioning import AgentCredential, TrialHandle from .runtime import RuntimeResult -DEFAULT_MAX_AGENT_ROUNDS = 32 +DEFAULT_MAX_AGENT_ROUNDS = 0 # 0 = unbounded (BUZZ_AGENT_MAX_ROUNDS=0); the trial budget is the clock # Container-side layout for the uploaded Buzz stack. REMOTE_ROOT = "/opt/buzz" REMOTE_BIN = f"{REMOTE_ROOT}/bin" @@ -80,8 +80,8 @@ def __init__( readiness_timeout_seconds: float = 60.0, poll_seconds: float = 1.0, ) -> None: - if max_agent_rounds <= 0: - raise ValueError("max_agent_rounds must be positive") + if max_agent_rounds < 0: + raise ValueError("max_agent_rounds must be >= 0 (0 = unbounded)") if readiness_timeout_seconds <= 0: raise ValueError("readiness_timeout_seconds must be positive") self.logs_dir = Path(logs_dir) diff --git a/benchmarks/harbor-buzz-orchestra/tests/test_container_runtime.py b/benchmarks/harbor-buzz-orchestra/tests/test_container_runtime.py index 5fc0e63e549..c0f5beeef22 100644 --- a/benchmarks/harbor-buzz-orchestra/tests/test_container_runtime.py +++ b/benchmarks/harbor-buzz-orchestra/tests/test_container_runtime.py @@ -247,7 +247,7 @@ async def test_forwarder_bridges_the_canonical_relay_address(tmp_path): rt._ws_authority("http://relay") -@pytest.mark.parametrize(("configured", "expected"), [(None, "32"), (7, "7")]) +@pytest.mark.parametrize(("configured", "expected"), [(None, "0"), (7, "7")]) async def test_launch_wires_the_desktop_environment(tmp_path, configured, expected): manifest = write_manifest(tmp_path) agent_class = manifest.roster[0] @@ -290,9 +290,12 @@ async def test_launch_wires_the_desktop_environment(tmp_path, configured, expect ) -def test_runtime_rejects_unbounded_agent_rounds(tmp_path): - with pytest.raises(ValueError, match="positive"): - runtime(tmp_path, max_agent_rounds=0) +def test_runtime_validates_construction_bounds(tmp_path): + # 0 is legal and means unbounded (BUZZ_AGENT_MAX_ROUNDS=0); the trial + # budget is the clock. Only negatives are rejected. + runtime(tmp_path, max_agent_rounds=0) + with pytest.raises(ValueError, match="unbounded"): + runtime(tmp_path, max_agent_rounds=-1) with pytest.raises(ValueError, match="positive"): runtime(tmp_path, readiness_timeout_seconds=0) diff --git a/crates/buzz-acp/src/acp.rs b/crates/buzz-acp/src/acp.rs index 93109fa94df..700d5e8dcfd 100644 --- a/crates/buzz-acp/src/acp.rs +++ b/crates/buzz-acp/src/acp.rs @@ -155,7 +155,7 @@ pub struct AcpClient { /// a `cancelled` outcome before the agent returns from `session/prompt`. pending_permission_id: Option, /// Whether we have already sent a response to the pending permission request. - /// Guards against double-response if a timeout fires after the rejection + /// Guards against double-response if a timeout fires after the allow_once /// response was written but before `pending_permission_id` was cleared. permission_responded: bool, /// The JSON-RPC id of the most recently sent `session/prompt` request. @@ -1162,8 +1162,7 @@ impl AcpClient { /// /// While waiting, handles: /// - `session/update` notifications → logged via tracing - /// - `session/request_permission` requests → rejected unless an owner has - /// already selected a non-interactive permission mode at session setup + /// - `session/request_permission` requests → auto-approved with `allow_once` /// - Any other messages → debug-logged and ignored; if they carry an `id` /// (i.e. they are requests, not notifications), a JSON-RPC -32601 error is sent. /// @@ -1871,12 +1870,12 @@ impl AcpClient { } } - /// Reject a `session/request_permission` request from the agent. + /// Auto-approve a `session/request_permission` request from the agent. /// - /// Buzz has no human permission prompt in this harness, so selecting - /// `allow_once` would turn any admitted prompt into an implicit approval. - /// Find `reject_once` by kind when the adapter offers it; otherwise use the - /// protocol's cancelled outcome, which is also fail-closed. + /// Finds the option with `kind == "allow_once"` and responds with its `optionId`. + /// If no `allow_once` option exists, falls back to `reject_once`. + /// + /// **Critical:** Never hardcode `optionId` — always find it dynamically by `kind`. /// /// The request `id` is stored as `serde_json::Value` to support both numeric /// and string IDs per JSON-RPC 2.0. @@ -1902,7 +1901,40 @@ impl AcpClient { options.len() ); - let response = permission_denial_response(&id, options)?; + // Find allow_once by kind — NEVER hardcode optionId. + let allow_once = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("allow_once")); + + let response = if let Some(opt) = allow_once { + let option_id = opt["optionId"] + .as_str() + .ok_or_else(|| AcpError::Protocol("allow_once option missing optionId".into()))?; + tracing::info!( + target: "acp::permission", + "auto-approving permission id={id} with allow_once optionId={option_id:?}" + ); + permission_response_selected(&id, option_id) + } else { + // No allow_once — fall back to reject_once. + tracing::warn!( + target: "acp::permission", + "no allow_once option found in permission request id={id}, falling back to reject_once" + ); + let reject = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("reject_once")); + + if let Some(opt) = reject { + let option_id = opt["optionId"].as_str().unwrap_or("reject"); + permission_response_selected(&id, option_id) + } else { + return Err(AcpError::Protocol( + "no suitable permission option found (neither allow_once nor reject_once)" + .into(), + )); + } + }; // Write the response first, then mark as responded. // @@ -2014,42 +2046,6 @@ fn permission_response_cancelled(id: &serde_json::Value) -> serde_json::Value { }) } -/// Choose the fail-closed response to a `session/request_permission` request. -/// -/// Buzz has no human permission prompt in this harness, so selecting -/// `allow_once` would turn any admitted prompt into an implicit approval. -/// Prefer the adapter's `reject_once` option — matched by `kind`, never by a -/// hardcoded `optionId` — and fall back to the protocol's cancelled outcome for -/// adapters that do not offer one. Both answers deny. -/// -/// Kept free of the client so the decision is testable without an agent -/// subprocess: `AcpClient` owns a real `Child` and its stdio pipes. -fn permission_denial_response( - id: &serde_json::Value, - options: &[serde_json::Value], -) -> Result { - let reject_once = options - .iter() - .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("reject_once")); - - let Some(opt) = reject_once else { - tracing::warn!( - target: "acp::permission", - "no reject_once option found in permission request id={id}, cancelling" - ); - return Ok(permission_response_cancelled(id)); - }; - - let option_id = opt["optionId"] - .as_str() - .ok_or_else(|| AcpError::Protocol("reject_once option missing optionId".into()))?; - tracing::info!( - target: "acp::permission", - "rejecting permission id={id} with reject_once optionId={option_id:?}" - ); - Ok(permission_response_selected(id, option_id)) -} - /// Full `session/new` response — session ID plus the raw JSON result. /// /// Callers use the extractor helpers to pull model info from `raw`. @@ -2304,96 +2300,63 @@ mod tests { assert_eq!(StopReason::from_str("Refusal"), Some(StopReason::Refusal)); } - fn options(json: &str) -> Vec { - serde_json::from_str(json).expect("option list") - } - - fn outcome(response: &serde_json::Value) -> Option<&str> { - response["result"]["outcome"]["outcome"].as_str() - } - - /// The offered `allow_once` and `allow_always` options must be ignored: - /// there is no human to click them, so choosing either would make every - /// admitted prompt an implicit approval. `optionId`s are deliberately - /// non-obvious to prove they are matched by `kind`, never hardcoded. #[test] - fn permission_requests_select_reject_once_not_allow_once() { - let options = options( + fn find_allow_once_by_kind_not_by_option_id() { + // optionId values are intentionally non-obvious to prove we don't hardcode them. + let options: Vec = serde_json::from_str( r#"[ {"optionId": "opt-reject-42", "name": "Reject", "kind": "reject_once"}, {"optionId": "opt-allow-99", "name": "Allow once", "kind": "allow_once"}, {"optionId": "opt-always-7", "name": "Always allow", "kind": "allow_always"} ]"#, - ); + ) + .unwrap(); - let response = - permission_denial_response(&serde_json::json!(7), &options).expect("denial response"); + let allow_once = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("allow_once")); - assert_eq!(outcome(&response), Some("selected")); - assert_eq!( - response["result"]["outcome"]["optionId"].as_str(), - Some("opt-reject-42"), - "must select reject_once even when allow options are offered" - ); + assert!(allow_once.is_some(), "should find allow_once option"); + let opt = allow_once.unwrap(); + // Found by kind, not by hardcoded optionId + assert_eq!(opt["kind"].as_str(), Some("allow_once")); + assert_eq!(opt["optionId"].as_str(), Some("opt-allow-99")); } - /// Fail-closed backstop: an adapter that offers no `reject_once` must still - /// be denied, via the protocol's cancelled outcome rather than an error or - /// an approval. #[test] - fn permission_request_without_reject_once_is_cancelled() { - let options = options( + fn find_allow_once_returns_none_when_absent() { + let options: Vec = serde_json::from_str( r#"[ - {"optionId": "opt-allow-99", "name": "Allow once", "kind": "allow_once"}, - {"optionId": "opt-always-7", "name": "Always allow", "kind": "allow_always"} + {"optionId": "reject-1", "name": "Reject", "kind": "reject_once"}, + {"optionId": "reject-always", "name": "Always reject", "kind": "reject_always"} ]"#, - ); - - let response = permission_denial_response(&serde_json::json!("req-1"), &options) - .expect("cancelled response"); - - assert_eq!(outcome(&response), Some("cancelled")); - assert_eq!( - response["id"].as_str(), - Some("req-1"), - "string ids must round-trip per JSON-RPC 2.0" - ); - } - - /// An empty option list is the degenerate form of the same backstop. - #[test] - fn permission_request_with_no_options_is_cancelled() { - let response = - permission_denial_response(&serde_json::json!(1), &[]).expect("cancelled response"); - - assert_eq!(outcome(&response), Some("cancelled")); - } - - /// A `reject_once` option missing its `optionId` is a protocol violation. - /// Erroring propagates to the caller, which tears the turn down — still no - /// approval is ever sent. - #[test] - fn reject_once_without_option_id_is_a_protocol_error() { - let options = options(r#"[{"name": "Reject", "kind": "reject_once"}]"#); + ) + .unwrap(); - let err = permission_denial_response(&serde_json::json!(1), &options) - .expect_err("missing optionId must error"); + let allow_once = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("allow_once")); - assert!(matches!(err, AcpError::Protocol(_)), "got {err:?}"); + assert!(allow_once.is_none()); } #[test] - fn find_reject_once_by_kind() { - let options = - options(r#"[{"optionId": "rej-x", "name": "Reject", "kind": "reject_once"}]"#); + fn find_reject_once_fallback_when_no_allow_once() { + let options: Vec = serde_json::from_str( + r#"[{"optionId": "rej-x", "name": "Reject", "kind": "reject_once"}]"#, + ) + .unwrap(); - let response = - permission_denial_response(&serde_json::json!(1), &options).expect("denial response"); + let allow_once = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("allow_once")); + assert!(allow_once.is_none()); - assert_eq!( - response["result"]["outcome"]["optionId"].as_str(), - Some("rej-x") - ); + let reject_once = options + .iter() + .find(|opt| opt.get("kind").and_then(|k| k.as_str()) == Some("reject_once")); + assert!(reject_once.is_some()); + assert_eq!(reject_once.unwrap()["optionId"].as_str(), Some("rej-x")); } #[test] diff --git a/crates/buzz-acp/src/config.rs b/crates/buzz-acp/src/config.rs index d9596858460..35aaec188db 100644 --- a/crates/buzz-acp/src/config.rs +++ b/crates/buzz-acp/src/config.rs @@ -116,6 +116,7 @@ impl std::fmt::Display for RespondTo { /// /// - `default` — agent's built-in behaviour (permission requests per tool call). /// - `acceptEdits` — auto-approve file edits, still ask for other tools. +/// - `bypassPermissions` — skip the permission flow entirely. /// - `dontAsk` — never prompt; reject anything that would require permission. /// - `plan` — planning-only mode (no tool execution). #[derive(Debug, Clone, Copy, PartialEq, clap::ValueEnum)] @@ -126,6 +127,9 @@ pub enum PermissionMode { /// Auto-approve file edits, still ask for other tools. #[value(alias = "acceptEdits")] AcceptEdits, + /// Skip the permission flow entirely. + #[value(alias = "bypassPermissions")] + BypassPermissions, /// Never prompt; reject anything that would require permission. #[value(alias = "dontAsk")] DontAsk, @@ -141,6 +145,7 @@ impl PermissionMode { match self { Self::Default => "default", Self::AcceptEdits => "acceptEdits", + Self::BypassPermissions => "bypassPermissions", Self::DontAsk => "dontAsk", Self::Plan => "plan", } @@ -427,12 +432,13 @@ pub struct CliArgs { /// Permission mode for agents that support `session/set_config_option` /// with `configId: "mode"` (e.g. `claude-agent-acp`). /// - /// Defaults to `dontAsk`, which rejects operations that need interactive - /// approval because Buzz does not expose a human permission prompt. + /// Defaults to `bypassPermissions` which skips the per-tool-call + /// permission flow. Set to `default` to restore the agent's built-in + /// behaviour. #[arg( long, env = "BUZZ_ACP_PERMISSION_MODE", - default_value = "dont-ask", + default_value = "bypass-permissions", value_enum )] pub permission_mode: PermissionMode, @@ -1463,7 +1469,7 @@ mod tests { memory_enabled: true, model: None, session_title: None, - permission_mode: PermissionMode::DontAsk, + permission_mode: PermissionMode::BypassPermissions, respond_to: RespondTo::Anyone, respond_to_allowlist: HashSet::new(), allowed_respond_to: Vec::new(), @@ -2264,6 +2270,10 @@ channels = "ALL" fn test_permission_mode_wire_strings() { assert_eq!(PermissionMode::Default.as_wire_str(), "default"); assert_eq!(PermissionMode::AcceptEdits.as_wire_str(), "acceptEdits"); + assert_eq!( + PermissionMode::BypassPermissions.as_wire_str(), + "bypassPermissions" + ); assert_eq!(PermissionMode::DontAsk.as_wire_str(), "dontAsk"); assert_eq!(PermissionMode::Plan.as_wire_str(), "plan"); } @@ -2271,6 +2281,7 @@ channels = "ALL" #[test] fn test_permission_mode_is_default() { assert!(PermissionMode::Default.is_default()); + assert!(!PermissionMode::BypassPermissions.is_default()); assert!(!PermissionMode::AcceptEdits.is_default()); assert!(!PermissionMode::DontAsk.is_default()); assert!(!PermissionMode::Plan.is_default()); @@ -2278,17 +2289,20 @@ channels = "ALL" #[test] fn test_permission_mode_display() { - assert_eq!(format!("{}", PermissionMode::DontAsk), "dontAsk"); + assert_eq!( + format!("{}", PermissionMode::BypassPermissions), + "bypassPermissions" + ); assert_eq!(format!("{}", PermissionMode::Default), "default"); } #[test] fn test_summary_includes_permission_mode() { let mut config = test_config(SubscribeMode::Mentions); - config.permission_mode = PermissionMode::DontAsk; + config.permission_mode = PermissionMode::BypassPermissions; let s = config.summary(); assert!( - s.contains("permission_mode=dontAsk"), + s.contains("permission_mode=bypassPermissions"), "summary should include permission_mode, got: {s}" ); } @@ -2305,9 +2319,9 @@ channels = "ALL" } #[test] - fn test_default_config_rejects_interactive_permissions() { + fn test_default_config_uses_bypass_permissions() { let config = test_config(SubscribeMode::Mentions); - assert_eq!(config.permission_mode, PermissionMode::DontAsk); + assert_eq!(config.permission_mode, PermissionMode::BypassPermissions); } #[test] @@ -2318,6 +2332,7 @@ channels = "ALL" let cases = [ ("default", PermissionMode::Default), ("accept-edits", PermissionMode::AcceptEdits), + ("bypass-permissions", PermissionMode::BypassPermissions), ("dont-ask", PermissionMode::DontAsk), ("plan", PermissionMode::Plan), ]; @@ -2332,12 +2347,14 @@ channels = "ALL" #[test] fn test_permission_mode_value_enum_camel_case_aliases() { - // Operators may set env vars using the camelCase wire-format strings. - // The #[value(alias)] attributes ensure these parse correctly. + // Operators may set env vars using the camelCase wire-format strings + // (e.g. BUZZ_ACP_PERMISSION_MODE=bypassPermissions). The #[value(alias)] + // attributes ensure these parse correctly. use clap::ValueEnum; let cases = [ ("default", PermissionMode::Default), ("acceptEdits", PermissionMode::AcceptEdits), + ("bypassPermissions", PermissionMode::BypassPermissions), ("dontAsk", PermissionMode::DontAsk), ("plan", PermissionMode::Plan), ]; @@ -2350,18 +2367,6 @@ channels = "ALL" } } - #[test] - fn test_permission_mode_rejects_unattended_bypass() { - use clap::ValueEnum; - - for input in ["bypass-permissions", "bypassPermissions"] { - assert!( - PermissionMode::from_str(input, true).is_err(), - "{input:?} must not disable the ACP permission boundary" - ); - } - } - /// Helper: resolve idle_timeout_secs using the same precedence logic as Config::from_args. /// Precedence: explicit --idle-timeout > --turn-timeout (deprecated) > `DEFAULT_IDLE_TIMEOUT_SECS`. fn resolve_idle_timeout(idle: Option, turn: Option) -> u64 { diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index 65c9dd6203c..b69a1f453a5 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -6199,7 +6199,7 @@ mod build_mcp_servers_tests { memory_enabled: false, model: None, session_title: None, - permission_mode: config::PermissionMode::DontAsk, + permission_mode: config::PermissionMode::BypassPermissions, respond_to: config::RespondTo::Anyone, respond_to_allowlist: std::collections::HashSet::new(), allowed_respond_to: vec![], @@ -6421,7 +6421,7 @@ mod error_outcome_emission_tests { memory_enabled: false, model: None, session_title: None, - permission_mode: config::PermissionMode::DontAsk, + permission_mode: config::PermissionMode::BypassPermissions, respond_to: config::RespondTo::Anyone, respond_to_allowlist: HashSet::new(), allowed_respond_to: vec![], diff --git a/crates/buzz-acp/src/pool.rs b/crates/buzz-acp/src/pool.rs index 8430307d9cd..ddc0330d9f2 100644 --- a/crates/buzz-acp/src/pool.rs +++ b/crates/buzz-acp/src/pool.rs @@ -1017,7 +1017,7 @@ async fn create_session_and_apply_model( // Apply permission mode if not the agent's built-in default AND the agent // advertises the requested mode in session/new. Agents that don't support // the mode (e.g., goose crashes on unrecognized set_config_option values) - // are safely skipped — the harness rejects interactive permission requests. + // are safely skipped — the harness auto-approves via handle_permission_request. if !ctx.permission_mode.is_default() && agent_supports_mode(&resp.raw, ctx.permission_mode.as_wire_str()) { @@ -1130,7 +1130,11 @@ async fn apply_model_switch( Ok(()) } -/// Check whether the agent's `session/new` response advertises a given mode ID +/// Set the session permission mode via `session/set_config_option`. +/// +/// Non-fatal for most errors: logs and proceeds. The agent falls back +/// to its default permission mode (`"default"`), which still works via +/// Check if the agent's `session/new` response advertises a given mode ID /// in `result.modes.availableModes[].id`. Returns `false` if the modes /// field is absent or the mode isn't listed. fn agent_supports_mode(session_new_result: &serde_json::Value, mode_wire: &str) -> bool { @@ -1146,11 +1150,7 @@ fn agent_supports_mode(session_new_result: &serde_json::Value, mode_wire: &str) .unwrap_or(false) } -/// Set the session permission mode via `session/set_config_option`. -/// -/// Non-fatal for most errors: logs and proceeds. The agent falls back to its -/// default mode, and any interactive permission request is rejected by -/// `handle_permission_request`. +/// per-tool auto-approval in `handle_permission_request`. /// /// **Fatal exception:** if the agent process exits (e.g., goose crashes on /// unrecognized methods), returns `Err(AgentExited)` so the caller can respawn. @@ -1190,7 +1190,7 @@ async fn apply_permission_mode( Ok(Err(e)) => { tracing::warn!( target: "pool::permission", - "failed to set permission mode {wire:?}: {e} — falling back to per-tool rejection" + "failed to set permission mode {wire:?}: {e} — falling back to per-tool auto-approval" ); } Err(_) => { diff --git a/crates/buzz-agent/src/llm.rs b/crates/buzz-agent/src/llm.rs index 289adbd1adb..c8c0a8550f7 100644 --- a/crates/buzz-agent/src/llm.rs +++ b/crates/buzz-agent/src/llm.rs @@ -1932,9 +1932,24 @@ fn classify_body_read_error( } } +/// Provider bodies that mean "this model cannot accept image input", the +/// signal the agent loop uses to strip rejected images from history and +/// continue the turn (see `replace_unsupported_images`). +/// +/// Deliberately tight, same doctrine as [`is_context_length_error`]: each +/// phrase is a verbatim capability rejection observed live. Misclassifying a +/// generic 400 as recoverable would mutate history for an error that removing +/// images cannot fix. fn is_unsupported_image_input_error(body: &str) -> bool { - body.to_ascii_lowercase() - .contains("no endpoints found that support image input") + let b = body.to_ascii_lowercase(); + // OpenRouter 404: no provider endpoint accepts images for this model. + b.contains("no endpoints found that support image input") + // OpenAI-compatible 400 from text-only single-model deployments, + // e.g. Crusoe serverless GLM: `"crusoeai/GLM-5.2-NVFP4 is not a + // multimodal model"`. Without this arm the 400 is terminal, the image + // stays in history, and every subsequent request in the session fails + // identically — the turn wedges until the harness/user gives up. + || b.contains("is not a multimodal model") } /// Build the terminal `AgentError::Llm` for a `post()` exit that has given up @@ -2129,6 +2144,13 @@ where "{status}: {body}" )))); } + // Image-capability rejection is equally recoverable and equally + // deterministic: a text-only deployment 400s the same request + // forever. Typed here (not just on the 404 arm) because + // OpenAI-compatible providers report it as a 400. + if status == 400 && is_unsupported_image_input_error(&body) { + return Err(PostError::Agent(AgentError::UnsupportedImageInput(body))); + } return Err(PostError::Agent(AgentError::Llm(format!( "{status}: {body}" )))); @@ -2181,8 +2203,33 @@ where } } } - return serde_json::from_slice(&buf) - .map_err(|e| PostError::Agent(AgentError::Llm(format!("json: {e}")))); + // A 2xx body that fails to parse (e.g. a provider flushing a + // truncated JSON document and closing the stream cleanly) is a + // transient upstream fault, not a request problem: retry it like a + // 5xx. Safe to re-send — a malformed body produced no parsed + // response, so no tool call was ever extracted from it, and the + // retried request is the identical completion POST captured in + // `body_bytes` at function entry. + match serde_json::from_slice(&buf) { + Ok(value) => return Ok(value), + Err(e) => { + if attempt + 1 < MAX_RETRIES { + tracing::warn!( + attempt = attempt + 1, + max_attempts = MAX_RETRIES, + error = %e, + "llm: malformed response body, retrying" + ); + backoff_with_jitter(attempt).await; + continue; + } + return Err(PostError::Agent(terminal_llm_error( + call_start.elapsed(), + attempt + 1, + &format!("json: {e}"), + ))); + } + } } // Unreachable in practice: every iteration either returns or continues. // A fallthrough here would mean MAX_RETRIES was 0, which is rejected at @@ -2535,6 +2582,12 @@ async fn openrouter_post( if status == 400 && is_context_length_error(&body) { return Err(AgentError::LlmContextExceeded(format!("{status}: {body}"))); } + // Same 400-shaped image rejection as the shared `post()` terminal: + // OpenRouter normally reports this as a 404 (handled above), but a + // BYOK/passthrough upstream can surface the provider's own 400. + if status == 400 && is_unsupported_image_input_error(&body) { + return Err(AgentError::UnsupportedImageInput(body)); + } return Err(AgentError::Llm(format!("{status}: {body}"))); } if let Some(len) = resp.content_length() { @@ -2582,7 +2635,31 @@ async fn openrouter_post( } } } - return serde_json::from_slice(&buf).map_err(|e| AgentError::Llm(format!("json: {e}"))); + // Same malformed-body retry as the shared `post()` terminal: a + // truncated 2xx JSON body is transient upstream trouble, and + // re-sending is provably tool-safe — nothing was parsed, so no tool + // call could have been extracted, and the retry re-uses the + // identical `body_bytes` completion request. + match serde_json::from_slice(&buf) { + Ok(value) => return Ok(value), + Err(e) => { + if attempt + 1 < MAX_RETRIES { + tracing::warn!( + attempt = attempt + 1, + max_attempts = MAX_RETRIES, + error = %e, + "llm: openrouter malformed response body, retrying" + ); + backoff_with_jitter(attempt).await; + continue; + } + return Err(terminal_llm_error( + call_start.elapsed(), + attempt + 1, + &format!("json: {e}"), + )); + } + } } Err(terminal_llm_error( call_start.elapsed(), @@ -4763,6 +4840,237 @@ mod tests { ); } + /// Regression (write-compressor, tb21-twins-1): a provider returning + /// HTTP 200 with a *truncated* JSON body (cleanly closed, correct + /// framing, unparseable content) previously surfaced as a terminal + /// `AgentError::Llm("json: EOF while parsing a value ...")` on the very + /// first attempt, killing the agent turn. A malformed body is transient + /// upstream trouble and must be retried like a 5xx. Re-sending is + /// tool-safe: nothing was parsed, so no tool call was extracted from the + /// bad body, and the retry replays the identical completion request. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn post_retries_malformed_json_body_and_succeeds() { + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/v1/x", listener.local_addr().unwrap()); + let accepts = Arc::new(AtomicU32::new(0)); + let accepts_srv = accepts.clone(); + + tokio::spawn(async move { + loop { + let (mut sock, _) = match listener.accept().await { + Ok(p) => p, + Err(_) => return, + }; + let n = accepts_srv.fetch_add(1, Ordering::SeqCst); + let mut buf = Vec::new(); + let mut tmp = [0u8; 4096]; + while !buf.windows(4).any(|w| w == b"\r\n\r\n") { + match sock.read(&mut tmp).await { + Ok(0) | Err(_) => return, + Ok(k) => buf.extend_from_slice(&tmp[..k]), + } + } + if n == 0 { + // First attempt: 200 OK with a truncated JSON document. + // Content-Length matches the bytes actually sent, so the + // body read completes cleanly — the fault is purely that + // the JSON is cut off mid-value. + let body = "{\"choices\":[{\"mess"; + let resp = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\ + Content-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = sock.write_all(resp.as_bytes()).await; + let _ = sock.shutdown().await; + continue; + } + // Subsequent attempts: complete valid JSON. + let body = "{\"ok\":true}"; + let resp = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\ + Content-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = sock.write_all(resp.as_bytes()).await; + let _ = sock.shutdown().await; + } + }); + + let client = Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .unwrap(); + let out = post( + &client, + &url, + &serde_json::json!({}), + false, + Duration::from_secs(5), + |b| b, + ) + .await + .expect("post should succeed after retrying the malformed body"); + assert_eq!(out, serde_json::json!({ "ok": true })); + assert_eq!( + accepts.load(Ordering::SeqCst), + 2, + "server must see exactly 2 attempts (malformed body retried once)" + ); + } + + /// A persistently malformed 200 body exhausts MAX_RETRIES and surfaces + /// the terminal error with the `json:` detail plus cumulative + /// duration/attempt count — never an early first-attempt death. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn post_exhausts_retries_on_persistent_malformed_json() { + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/v1/x", listener.local_addr().unwrap()); + let accepts = Arc::new(AtomicU32::new(0)); + let accepts_srv = accepts.clone(); + + tokio::spawn(async move { + loop { + let (mut sock, _) = match listener.accept().await { + Ok(p) => p, + Err(_) => return, + }; + accepts_srv.fetch_add(1, Ordering::SeqCst); + let mut buf = Vec::new(); + let mut tmp = [0u8; 4096]; + while !buf.windows(4).any(|w| w == b"\r\n\r\n") { + match sock.read(&mut tmp).await { + Ok(0) | Err(_) => return, + Ok(k) => buf.extend_from_slice(&tmp[..k]), + } + } + let body = "{\"choices\":[{\"mess"; + let resp = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\ + Content-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = sock.write_all(resp.as_bytes()).await; + let _ = sock.shutdown().await; + } + }); + + let client = Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .unwrap(); + let err = post( + &client, + &url, + &serde_json::json!({}), + false, + Duration::from_secs(5), + |b| b, + ) + .await + .unwrap_err(); + match &err { + PostError::Agent(AgentError::Llm(msg)) => { + assert!( + msg.contains("json:"), + "expected the json parse detail, got: {msg}" + ); + assert!( + msg.contains("cumulative") && msg.contains("3 attempts"), + "expected cumulative duration + exact attempt count, got: {msg}" + ); + } + other => panic!("expected PostError::Agent(AgentError::Llm), got: {other:?}"), + } + assert_eq!( + accepts.load(Ordering::SeqCst), + MAX_RETRIES, + "server must see exactly MAX_RETRIES attempts — malformed bodies must be retried" + ); + } + + /// Same regression coverage for `openrouter_post`, which carries its own + /// retry loop: a truncated 200 body on the first attempt is retried and + /// the call succeeds on the second. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn openrouter_post_retries_malformed_json_body_and_succeeds() { + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/v1/x", listener.local_addr().unwrap()); + let accepts = Arc::new(AtomicU32::new(0)); + let accepts_srv = accepts.clone(); + + tokio::spawn(async move { + loop { + let (mut sock, _) = match listener.accept().await { + Ok(p) => p, + Err(_) => return, + }; + let n = accepts_srv.fetch_add(1, Ordering::SeqCst); + let mut buf = Vec::new(); + let mut tmp = [0u8; 4096]; + while !buf.windows(4).any(|w| w == b"\r\n\r\n") { + match sock.read(&mut tmp).await { + Ok(0) | Err(_) => return, + Ok(k) => buf.extend_from_slice(&tmp[..k]), + } + } + if n == 0 { + let body = "{\"choices\":[{\"mess"; + let resp = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\ + Content-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = sock.write_all(resp.as_bytes()).await; + let _ = sock.shutdown().await; + continue; + } + let body = r#"{"choices":[{"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}]}"#; + let resp = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\ + Content-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = sock.write_all(resp.as_bytes()).await; + let _ = sock.shutdown().await; + } + }); + + let client = Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .unwrap(); + let out = openrouter_post(&client, &url, &json!({}), "key", Duration::from_secs(5)) + .await + .expect("openrouter_post should succeed after retrying the malformed body"); + assert!(out.is_object(), "expected a JSON object: {out:?}"); + assert_eq!( + accepts.load(Ordering::SeqCst), + 2, + "server must see exactly 2 attempts (malformed body retried once)" + ); + } + /// A body-read timeout on the first attempt triggers a retry under an /// escalated budget, and the call succeeds on the second attempt. /// @@ -7544,6 +7852,73 @@ mod tests { ); } + /// OpenAI-compatible text-only deployments report the image rejection as a + /// 400, not OpenRouter's 404 — Crusoe serverless GLM answers + /// `"crusoeai/GLM-5.2-NVFP4 is not a multimodal model"` to every request + /// whose history contains an image. Before the 400 arm existed, this fell + /// through to terminal `AgentError::Llm`: the image stayed in history and + /// every later call in the session failed identically (measured live: + /// 8 wedged benchmark trials, 40 min of doomed retries each). Asserted + /// through `complete()` so the arm's return path into the convergence + /// mapper is covered, same doctrine as the context-400 tests above. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn openai_400_unsupported_image_is_typed_through_complete() { + let (base_url, captured) = spawn_sequence_stub(vec![StubHttpResponse { + status: 400, + body: json!({"error":{"message":"crusoeai/GLM-5.2-NVFP4 is not a multimodal model","type":"invalid_request_error"}}), + }]) + .await; + let mut c = cfg(Provider::OpenAi); + c.base_url = base_url; + let llm = Llm::new(&c).unwrap(); + let err = complete_model(&llm, &c, "gpt-probe-model") + .await + .unwrap_err(); + assert!( + matches!(&err, AgentError::UnsupportedImageInput(s) if s.contains("not a multimodal model")), + "a text-only deployment's 400 must reach the history-recovery path: got {err:?}" + ); + assert_eq!( + captured.lock().await.len(), + 1, + "a deterministic capability rejection must not be retried" + ); + } + + /// Same 400-shaped rejection at the OpenRouter terminal, which has its own + /// status ladder: a BYOK/passthrough upstream can surface the provider's + /// own 400 body instead of OpenRouter's 404 routing error. + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn openrouter_post_400_unsupported_image_is_typed_and_not_retried() { + let (url, _captured, attempts) = spawn_openrouter_stub(vec![CannedResponse::new( + 400, + r#"{"error":{"message":"crusoeai/GLM-5.2-NVFP4 is not a multimodal model"}}"#, + )]) + .await; + let http = Client::builder() + .timeout(Duration::from_secs(5)) + .build() + .unwrap(); + let err = openrouter_post( + &http, + &format!("{url}/x"), + &json!({}), + "key", + Duration::from_secs(5), + ) + .await + .unwrap_err(); + assert!( + matches!(&err, AgentError::UnsupportedImageInput(s) if s.contains("not a multimodal model")), + "image rejection must reach the history-recovery path: got {err:?}" + ); + assert_eq!( + attempts.load(std::sync::atomic::Ordering::SeqCst), + 1, + "a deterministic capability rejection must not be retried" + ); + } + /// Every other 404 still maps to `LlmModelNotFound`, including one that /// shares the `No endpoints found` prefix but is about the model rather than /// the parameters — the discriminator is narrow enough that a genuinely diff --git a/crates/buzz-relay/CHANGELOG.md b/crates/buzz-relay/CHANGELOG.md index c0d6ab9ccd5..7ee7e9a303b 100644 --- a/crates/buzz-relay/CHANGELOG.md +++ b/crates/buzz-relay/CHANGELOG.md @@ -1,5 +1,114 @@ # Changelog +## relay-v0.2.1 + +- fix(sdk): preserve self-mention p tags in message and forum event builders ([#4975](https://github.com/block/buzz/pull/4975)) ([`78c87ae20e`](https://github.com/block/buzz/commit/78c87ae20e182fffdd99744d6c9ff99df82b159c)) +- feat(desktop): adding rich link previews to messages ([#3818](https://github.com/block/buzz/pull/3818)) ([`1922d49cb2`](https://github.com/block/buzz/commit/1922d49cb200a3382a91ec253f530b44dfda5f55)) +- feat(relay): accept kind:30179 private managed-agent events at ingest ([#5133](https://github.com/block/buzz/pull/5133)) ([`ad923353a2`](https://github.com/block/buzz/commit/ad923353a24b784df13a7c88757d6b24ebe36299)) +- fix(media): require authenticated reads ([#4610](https://github.com/block/buzz/pull/4610)) ([`769ac70b74`](https://github.com/block/buzz/commit/769ac70b741e3ad6809bff14eba29d3dd2cbd318)) +- feat(identity): recover desktop identity from a signed-in phone ([#4845](https://github.com/block/buzz/pull/4845)) ([`6eb65919f1`](https://github.com/block/buzz/commit/6eb65919f1eabd46b3850c15eefab31092dd500b)) +- ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes ([#3862](https://github.com/block/buzz/pull/3862)) ([`38bf642fcf`](https://github.com/block/buzz/commit/38bf642fcfa7a9fc1e06d6cf87d66ae94da29341)) +- relay: fuzz WebSocket 1012 restart-close timing on graceful drain (BUZZ_DRAIN_JITTER_MS) ([#4542](https://github.com/block/buzz/pull/4542)) ([`e14fff74d0`](https://github.com/block/buzz/commit/e14fff74d00623acd30945eec5be366e25b0cf09)) +- fix(reactions): support max-length custom emoji ([#3833](https://github.com/block/buzz/pull/3833)) ([`2ea9385015`](https://github.com/block/buzz/commit/2ea9385015fb922de2adf0a53e86fc5a21d07b90)) +- fix(channels): restrict private-channel invitations ([#4612](https://github.com/block/buzz/pull/4612)) ([`efe1893dd3`](https://github.com/block/buzz/commit/efe1893dd372cfb92ed2e8a3ada2ed7b62c9477a)) +- fix(workflow): bind trigger author to the signed event ([#4607](https://github.com/block/buzz/pull/4607)) ([`885bed35ee`](https://github.com/block/buzz/commit/885bed35eee3f933c48d333c8979fdbc038e98b9)) +- fix(git): revoke access for banned relay members ([#4608](https://github.com/block/buzz/pull/4608)) ([`997b8caaa4`](https://github.com/block/buzz/commit/997b8caaa4c9e5af69dd8a496b4995d09a69f694)) +- Define private managed agent wire protocol ([#4593](https://github.com/block/buzz/pull/4593)) ([`067c085f37`](https://github.com/block/buzz/commit/067c085f37d9dcb2f598b0e2a6b6653903364783)) +- perf(relay): index channel-id lookups and skip trace-only reads ([#4647](https://github.com/block/buzz/pull/4647)) ([`bc9e6528a7`](https://github.com/block/buzz/commit/bc9e6528a7ba6007c5a25f6a0aca9c05d72e9d2c)) +- Polish mobile inbox and media flows ([#4512](https://github.com/block/buzz/pull/4512)) ([`feccf4eabc`](https://github.com/block/buzz/commit/feccf4eabc23fdba94ce3537a194357ed17b197c)) +- fix(git): allow deleting the default branch ([#4297](https://github.com/block/buzz/pull/4297)) ([`fc598f5f8d`](https://github.com/block/buzz/commit/fc598f5f8d70728d11d0712b9fa8e3acc44ea4c3)) +- feat(projects): add buzz projects CLI commands (NIP-MP kind:30621) ([#4020](https://github.com/block/buzz/pull/4020)) ([`b7bb15122e`](https://github.com/block/buzz/commit/b7bb15122e8a2053b545dc2210afc167f6c7a626)) +- perf(relay): serve relay-membership checks from the read replica ([#4124](https://github.com/block/buzz/pull/4124)) ([`ac4fa13b8e`](https://github.com/block/buzz/commit/ac4fa13b8e4d947071d57deb6918dcf12bf74961)) +- fix(relay): allow open relays to set their NIP-11 workspace icon (kind:9033) ([#3998](https://github.com/block/buzz/pull/3998)) ([`5765fc74b7`](https://github.com/block/buzz/commit/5765fc74b77224f0207ddd4b41736a5ff18d333d)) +- feat(relay): accept kind:30621 multi-repo projects at ingest ([#3171](https://github.com/block/buzz/pull/3171)) ([`cb9701cd30`](https://github.com/block/buzz/commit/cb9701cd30fb344bf134585634a09007f3155bfb)) +- feat(relay): raise hosted community limit to five ([#3829](https://github.com/block/buzz/pull/3829)) ([`10d5a26414`](https://github.com/block/buzz/commit/10d5a26414dc90dc89fd27de74b21e105d4fa622)) +- fix(relay): align NIP-11 max_limit with REQ ceiling ([#3635](https://github.com/block/buzz/pull/3635)) ([`23f0c26b1c`](https://github.com/block/buzz/commit/23f0c26b1ceba8e07bf3c160a1e08c7bda82ccd9)) +- feat(relay): gate kind 30178 team-catalog reads behind the shared tag ([#3358](https://github.com/block/buzz/pull/3358)) ([`114d40d9d3`](https://github.com/block/buzz/commit/114d40d9d37f05eff83ee90347ed93fb3da512c5)) +- fix(db): isolate usage metrics advisory-lock test on scratch DB ([#3670](https://github.com/block/buzz/pull/3670)) ([`dba97eecd9`](https://github.com/block/buzz/commit/dba97eecd9d8659c9c816cd6666fa6d687b6bca1)) +- perf(presence): reduce heartbeat frequency ([#3783](https://github.com/block/buzz/pull/3783)) ([`bf139e8d0b`](https://github.com/block/buzz/commit/bf139e8d0bdba10df9a5adbf16843140e0a78a59)) +- feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) ([#3741](https://github.com/block/buzz/pull/3741)) ([`4933672eb4`](https://github.com/block/buzz/commit/4933672eb4589e7208b312829ebddcd10dfa9dd3)) +- feat(replica): portable heartbeat-token fence with snapshot-local reader routing ([#3268](https://github.com/block/buzz/pull/3268)) ([`63496cc1d4`](https://github.com/block/buzz/commit/63496cc1d4c6f1b7c613801bdcc694169dcf391a)) +- fix(git): channel binding tooling + author remediation for unbound repos ([#3626](https://github.com/block/buzz/pull/3626)) ([`788b3c002b`](https://github.com/block/buzz/commit/788b3c002bd2509455444f57f8a03a054b4b496a)) +- feat: configure S3 URL addressing style ([#3400](https://github.com/block/buzz/pull/3400)) ([`7012d86d52`](https://github.com/block/buzz/commit/7012d86d52fd188b27c7beedeaa132d9c1f61fa8)) +- feat(tracing): correlate trace IDs in relay logs ([#3608](https://github.com/block/buzz/pull/3608)) ([`005b5b819a`](https://github.com/block/buzz/commit/005b5b819a98ce85d4d80cd81b258fb6f9b8d51e)) +- fix(relay): avoid subscription lock inversion ([#3413](https://github.com/block/buzz/pull/3413)) ([`22be8bb351`](https://github.com/block/buzz/commit/22be8bb35177e27efc2dca2534df9a8dd871eae0)) +- feat(cli): add users set-status command for NIP-38 profile status ([#3253](https://github.com/block/buzz/pull/3253)) ([`60158fce3e`](https://github.com/block/buzz/commit/60158fce3e670f11bb35d42627857ccaea50ff06)) +- feat(relay): make Postgres pool size configurable, default 50 ([#3191](https://github.com/block/buzz/pull/3191)) ([`2ce2d71cc3`](https://github.com/block/buzz/commit/2ce2d71cc38a9657eaf344c10e07f155b8a18615)) +- feat(tracing): add datastore tracing plumbing ([#2760](https://github.com/block/buzz/pull/2760)) ([`e94b9aeda0`](https://github.com/block/buzz/commit/e94b9aeda0b2272d36e3744e78680be69295b8b5)) +- feat(invites): add use-limited invite links ([#3141](https://github.com/block/buzz/pull/3141)) ([`d500c2d5cf`](https://github.com/block/buzz/commit/d500c2d5cf5d9aabe0ca4ebebfcafdbe5f5b7fd3)) +- feat(admin): show reported message content in report detail ([#3149](https://github.com/block/buzz/pull/3149)) ([`f069a85503`](https://github.com/block/buzz/commit/f069a8550373328babe4239ed614fcdf884721e2)) +- resolve findings ([#3150](https://github.com/block/buzz/pull/3150)) ([`9b0f744804`](https://github.com/block/buzz/commit/9b0f744804697b802f7afb88947194702765c78d)) +- Revert "fix(cli,relay): resolve agents by verified owner" ([#3168](https://github.com/block/buzz/pull/3168)) ([`a041e2d21e`](https://github.com/block/buzz/commit/a041e2d21e292a271fdfc26f0cdcdd0456f815c5)) +- fix(cli,relay): resolve agents by verified owner ([#2615](https://github.com/block/buzz/pull/2615)) ([`c3084b36d9`](https://github.com/block/buzz/commit/c3084b36d975259f2dfeee8edc9131b40a8bce83)) +- fix(security): enforce durable community ban on NIP-43 relay-admin kinds 9030-9033 ([#3128](https://github.com/block/buzz/pull/3128)) ([`e2e0079101`](https://github.com/block/buzz/commit/e2e007910114ddf7c5a4e93bb03f6afe13552e92)) +- fix(security): authorize kind:9000 role changes in both directions ([#3017](https://github.com/block/buzz/pull/3017)) ([`00ecf2cac7`](https://github.com/block/buzz/commit/00ecf2cac7544d986b4eb111ad0a8b1d7560791f)) +- feat(desktop): handle project work from Inbox ([#3117](https://github.com/block/buzz/pull/3117)) ([`c5c4f390b6`](https://github.com/block/buzz/commit/c5c4f390b6713256e2efb8394c59823ebad73db6)) +- feat(relay): make per-owner community limit configurable via BUZZ_MAX_COMMUNITIES_PER_OWNER ([#2599](https://github.com/block/buzz/pull/2599)) ([`2a051a404d`](https://github.com/block/buzz/commit/2a051a404dcde42dddbff2a0b33f717ffe9cf999)) +- feat(relay): add author-only-unless-shared read gate for kind 30175 ([#2768](https://github.com/block/buzz/pull/2768)) ([`ab3af82871`](https://github.com/block/buzz/commit/ab3af828714ab699dfc87644d234014987a4fe6b)) +- fix(core): block IPv6 transition SSRF targets ([#2801](https://github.com/block/buzz/pull/2801)) ([`c26bf5945d`](https://github.com/block/buzz/commit/c26bf5945d8f2ef19746a78e80a7c1dae2ef3db9)) +- fix(workflow): bypass system proxies for webhooks ([#2800](https://github.com/block/buzz/pull/2800)) ([`60a171b19e`](https://github.com/block/buzz/commit/60a171b19efd515d9213b535d52a2bcbec3ff2fe)) +- fix(audit): hash created_at at the precision Postgres stores ([#2638](https://github.com/block/buzz/pull/2638)) ([`264a56a226`](https://github.com/block/buzz/commit/264a56a2260ac87350bfe1f5d3ec3d89615eb47c)) +- feat(desktop): make pull request reviews actionable ([#2510](https://github.com/block/buzz/pull/2510)) ([`9081ab0ec9`](https://github.com/block/buzz/commit/9081ab0ec9c5d91548c7f5ff52eba6cca4788dd0)) +- fix(relay): decompress gzip-encoded git smart-HTTP request bodies ([#2670](https://github.com/block/buzz/pull/2670)) ([`5ca36e7b91`](https://github.com/block/buzz/commit/5ca36e7b919097733868764d8e0073e99c3206c3)) +- fix(sharing): preserve agent/team snapshot tEXt chunks through media sanitization ([#2438](https://github.com/block/buzz/pull/2438)) ([`b096b0a15a`](https://github.com/block/buzz/commit/b096b0a15af4c4566365c5b1efe7f39b700222ed)) +- fix(relay): send 1012 restart close to all clients on graceful drain ([#2575](https://github.com/block/buzz/pull/2575)) ([`1911c69aa2`](https://github.com/block/buzz/commit/1911c69aa2912c1408bd6b21759b657458fb43af)) +- fix(media): sanitize animated image uploads ([#2524](https://github.com/block/buzz/pull/2524)) ([`8f8f5fa5a4`](https://github.com/block/buzz/commit/8f8f5fa5a4b2463cdc6c2a527acb7086150cdaae)) +- fix(channels): strip leading hash prefixes from names ([#2250](https://github.com/block/buzz/pull/2250)) ([`d0ab3fdb05`](https://github.com/block/buzz/commit/d0ab3fdb054e0cfedbf21e4c5143ad6c671c10cc)) +- feat(relay): make Redis pool size configurable, default 16 ([#2521](https://github.com/block/buzz/pull/2521)) ([`bcc3e13069`](https://github.com/block/buzz/commit/bcc3e1306946528102bb26be9a7c41299e2f8e00)) +- feat(desktop+acp): spawn a harness per (agent, community) pair at GUI startup — warm sockets, lazy LLM pool ([#2122](https://github.com/block/buzz/pull/2122)) ([`61cc738ee8`](https://github.com/block/buzz/commit/61cc738ee8991e92563136de4b77e54cb9756420)) +- feat(media): add S3-truth per-community storage sweep ([#2044](https://github.com/block/buzz/pull/2044)) ([`bd37a4d584`](https://github.com/block/buzz/commit/bd37a4d584fefc1d13ad8abadf6e890e66183072)) +- feat(relay): log NIP-98 pubkey attribution on HTTP bridge requests ([#2206](https://github.com/block/buzz/pull/2206)) ([`7e34bee62c`](https://github.com/block/buzz/commit/7e34bee62cacaa9d8a96c14d5892a471b59a1983)) +- Revert "feat(relay): inventory unreachable Git objects" ([#2275](https://github.com/block/buzz/pull/2275)) ([`0fb820f9bf`](https://github.com/block/buzz/commit/0fb820f9bfbd7e19e48f9826e332920c2ee2c229)) +- feat(relay): inventory unreachable Git objects ([#2264](https://github.com/block/buzz/pull/2264)) ([`3afc9dae15`](https://github.com/block/buzz/commit/3afc9dae159262220c4149e9c8add50772869318)) +- relay: add author_type label to buzz_events_stored_total ([#2243](https://github.com/block/buzz/pull/2243)) ([`b9f54c43fe`](https://github.com/block/buzz/commit/b9f54c43fe2bcd0eb8fb3b76914e9aa0c31f6927)) +- fix(git): make project branch workflows reliable ([#2213](https://github.com/block/buzz/pull/2213)) ([`166f27be4b`](https://github.com/block/buzz/commit/166f27be4bc1abf2d465493bf2137353045399dc)) +- feat(cli): manage repository protection rules ([#2193](https://github.com/block/buzz/pull/2193)) ([`f94324598d`](https://github.com/block/buzz/commit/f94324598d84b2db9a05a3fa1f855970c4c5b575)) +- feat(cli): add agents archive/unarchive/archived subcommands ([#2173](https://github.com/block/buzz/pull/2173)) ([`7d7992067b`](https://github.com/block/buzz/commit/7d7992067b2914b582b7e6d31a6174603b480b4b)) +- fix(mobile): sanitize Android image uploads ([#2188](https://github.com/block/buzz/pull/2188)) ([`ee21da90bd`](https://github.com/block/buzz/commit/ee21da90bd6b1da6bfaaf22ba00749398aaa9640)) +- fix(cli): paginate channel directory queries ([#2181](https://github.com/block/buzz/pull/2181)) ([`03fe19d603`](https://github.com/block/buzz/commit/03fe19d6033094ae2ec4c89c26eb23174ef53daa)) +- fix(mobile): image upload fails due to unstripped metadata ([#2185](https://github.com/block/buzz/pull/2185)) ([`37f15b2001`](https://github.com/block/buzz/commit/37f15b20019169363b697aee41c99573b7bc3f24)) +- perf(relay): compact Git packs before manifest limits ([#2172](https://github.com/block/buzz/pull/2172)) ([`80e0ab16b0`](https://github.com/block/buzz/commit/80e0ab16b03c656ec8def18bedc27eaf29c02867)) +- perf(relay): cache Git pack hydration ([#2169](https://github.com/block/buzz/pull/2169)) ([`a4d82ec722`](https://github.com/block/buzz/commit/a4d82ec7226e685a933dbd829b6bb8bce0787b4e)) +- fix(relay): bound and observe Git read operations ([#2167](https://github.com/block/buzz/pull/2167)) ([`5f7c93d9c1`](https://github.com/block/buzz/commit/5f7c93d9c12ce7894288ae47f3fe223fcff2dce3)) +- relay: gate push enqueue on live leases; batch matcher pipeline (T1b/T1a-repair/T2b) ([#2145](https://github.com/block/buzz/pull/2145)) ([`e43b2d5aac`](https://github.com/block/buzz/commit/e43b2d5aac0d1f2b6b623b04f7af5a51f77da8c6)) +- relay: add audit logging disable switch ([#2134](https://github.com/block/buzz/pull/2134)) ([`bf5acabdde`](https://github.com/block/buzz/commit/bf5acabdde44aa133bdcbafcf9e1a4ff752c3302)) +- relay: skip TTL deadline bump for known-permanent channels (T1a write-amp) ([#2125](https://github.com/block/buzz/pull/2125)) ([`2e936d439c`](https://github.com/block/buzz/commit/2e936d439ce29182b48086f9f8a7a3ffe3b9b345)) +- fix(git): carry NIP-OA delegation in auth event ([#2120](https://github.com/block/buzz/pull/2120)) ([`c12257d57a`](https://github.com/block/buzz/commit/c12257d57a54d5c1e16435440b02beb5d1c057b8)) +- Route lag-tolerant reads to an optional Postgres read replica ([#2084](https://github.com/block/buzz/pull/2084)) ([`29c48883d3`](https://github.com/block/buzz/commit/29c48883d30e6feed75e33490571ca96082c6282)) +- fix: recover community access visibility ([#2074](https://github.com/block/buzz/pull/2074)) ([`ca384d082d`](https://github.com/block/buzz/commit/ca384d082d9804ec53a3fd12ccbf4a0846b21d92)) +- feat: proxy feedback-scoped admin attachments ([#2059](https://github.com/block/buzz/pull/2059)) ([`d7f918e3cb`](https://github.com/block/buzz/commit/d7f918e3cbcc4d30f222d0f4ae836de808f859a2)) +- feat: add read-only deployment moderation dashboard ([#1999](https://github.com/block/buzz/pull/1999)) ([`68e670e001`](https://github.com/block/buzz/commit/68e670e001d2bed2cf141095926feaf482c3bed8)) +- Bug-bash round 2: table scroll, Goose instructions, workflow mention wake ([#2034](https://github.com/block/buzz/pull/2034)) ([`64b8fea6dc`](https://github.com/block/buzz/commit/64b8fea6dce3be684aa0bac5dbd701e46dc7e432)) +- Strip media metadata on clients and reject it at the relay ([#2006](https://github.com/block/buzz/pull/2006)) ([`5cfd69cb0c`](https://github.com/block/buzz/commit/5cfd69cb0cf1dc63d718454defe3b8a8aaf5f15b)) +- [codex] Hold Git concurrency permits through streaming (BUZZ-SEC-018) ([#1916](https://github.com/block/buzz/pull/1916)) ([`7baea42abb`](https://github.com/block/buzz/commit/7baea42abbbb794e6e5ab0e9df11e2d1b0550d0b)) +- [codex] Enforce shared relay admission limits (BUZZ-SEC-019) ([#1917](https://github.com/block/buzz/pull/1917)) ([`73fc0ec6cf`](https://github.com/block/buzz/commit/73fc0ec6cf58a79bfc65e42faba457bf49c2d232)) +- [codex] Block banned actors from moderation commands (BUZZ-SEC-007) ([#1915](https://github.com/block/buzz/pull/1915)) ([`caa195ca58`](https://github.com/block/buzz/commit/caa195ca58ea49cf8ed9c3ede55d6a2e4ed37096)) +- [codex] Fix relay WebSocket admission limits ([#1682](https://github.com/block/buzz/pull/1682)) ([`d3ce971fc7`](https://github.com/block/buzz/commit/d3ce971fc75a34162d5498c27ac4a1c30236630a)) +- feat: add invite QR and mobile direct join ([#1957](https://github.com/block/buzz/pull/1957)) ([`648cbf3610`](https://github.com/block/buzz/commit/648cbf36109d97be6bd8530e77073d1c7e6008a0)) +- fix(join-policy): require legal consent on hosted invites ([#1987](https://github.com/block/buzz/pull/1987)) ([`2e1577f76f`](https://github.com/block/buzz/commit/2e1577f76f5105ddacda7be884518574ca8d6b96)) +- [codex] Prevent actor-tag UI impersonation ([#1931](https://github.com/block/buzz/pull/1931)) ([`c540ec9678`](https://github.com/block/buzz/commit/c540ec967869ef0f4eef90439bf70929fc74f7f6)) +- Scope relay runtime state by community ([#1658](https://github.com/block/buzz/pull/1658)) ([`d52dedb06f`](https://github.com/block/buzz/commit/d52dedb06fc2c7692c6d9225c7a08b41a509633a)) +- Apply optional relay join policy across join flows ([#1894](https://github.com/block/buzz/pull/1894)) ([`6c2d667575`](https://github.com/block/buzz/commit/6c2d667575cbc372ba42d26134448660fb1d2ee9)) +- feat(media): require auth for relay media reads ([#1926](https://github.com/block/buzz/pull/1926)) ([`f308762852`](https://github.com/block/buzz/commit/f3087628524951de91028c9d263bcd0d0a727fab)) +- feat(relay): add community unarchive endpoint ([#1908](https://github.com/block/buzz/pull/1908)) ([`6b9641db2b`](https://github.com/block/buzz/commit/6b9641db2b4709b71622b7ef0b799117a0605ca6)) +- feat(relay): gate Git web GUI separately ([#1901](https://github.com/block/buzz/pull/1901)) ([`34dc7dec75`](https://github.com/block/buzz/commit/34dc7dec75285ab6f2107ce5cad170b80b92206a)) +- mesh: upgrade runtime, enforce membership, add shared compute provider ([#1656](https://github.com/block/buzz/pull/1656)) ([`54638ff4bb`](https://github.com/block/buzz/commit/54638ff4bb5af2d3d3759b44118b43052f814bb1)) +- Route Git scratch through configured volume ([#1884](https://github.com/block/buzz/pull/1884)) ([`2318b3096c`](https://github.com/block/buzz/commit/2318b3096c585f8d31bd43e27dc5d6305c5fe20d)) +- feat(relay): gate usage metrics behind stable leader ([#1814](https://github.com/block/buzz/pull/1814)) ([`59e9821503`](https://github.com/block/buzz/commit/59e9821503a2fe23fc4630aa0f36bb252ae4566f)) +- Relay mesh: cross-pod tunnel + huddle transport (buzz-relay-mesh) ([#1670](https://github.com/block/buzz/pull/1670)) ([`ccb021d713`](https://github.com/block/buzz/commit/ccb021d71339009aabedc383c8f3d8e5c23e1e42)) +- feat(push): deliver accepted relay events as wakes ([#1866](https://github.com/block/buzz/pull/1866)) ([`bffbc5f22c`](https://github.com/block/buzz/commit/bffbc5f22cc80e9a07dedc622798347d598a215c)) +- fix(db): resolve duplicate migration version ([#1863](https://github.com/block/buzz/pull/1863)) ([`08ad38a07f`](https://github.com/block/buzz/commit/08ad38a07f0c49bb3f20b775b8f534f1cfa529c3)) +- Add private product feedback sidecar ([#1857](https://github.com/block/buzz/pull/1857)) ([`af190c93e1`](https://github.com/block/buzz/commit/af190c93e1048af64c3fbfb3831c689cb703997c)) +- feat(relay): add durable community archival ([#1834](https://github.com/block/buzz/pull/1834)) ([`2b15a72675`](https://github.com/block/buzz/commit/2b15a726750dbd7437711050cd3241b679dff317)) +- feat(push): add public APNs gateway ([#1770](https://github.com/block/buzz/pull/1770)) ([`1c006822e4`](https://github.com/block/buzz/commit/1c006822e4484d68e33fce14f9139c2f70ce9d66)) +- feat(relay): add atomic community ownership transfer ([#1845](https://github.com/block/buzz/pull/1845)) ([`52e42ccb9f`](https://github.com/block/buzz/commit/52e42ccb9fc85445814614c72d40f346e986152b)) +- Bound NIP-RS retention and search indexing ([#1771](https://github.com/block/buzz/pull/1771)) ([`1b4703021d`](https://github.com/block/buzz/commit/1b4703021dbfd37dc31845223dba9ba182e4647f)) +- Add optional standalone pairing relay to Helm chart ([#1799](https://github.com/block/buzz/pull/1799)) ([`9b47c8548f`](https://github.com/block/buzz/commit/9b47c8548fd061fbb806ea8b9ddee831c19cf80e)) +- fix(relay): publish membership snapshot on provisioning ([#1761](https://github.com/block/buzz/pull/1761)) ([`0950d392b7`](https://github.com/block/buzz/commit/0950d392b7a862694c95cbea1cec45985ee42996)) +- feat(relay): per-community usage metrics ([#1723](https://github.com/block/buzz/pull/1723)) ([`620822899a`](https://github.com/block/buzz/commit/620822899a6373fa3a17a87815cd7cade25ed332)) +- refactor(desktop): remove vestigial MCP toolsets config ([#1776](https://github.com/block/buzz/pull/1776)) ([`dfec75b3c0`](https://github.com/block/buzz/commit/dfec75b3c0b8080529e4d9089d4ed80e3902aaed)) + + ## relay-v0.2.0 - feat: relay invite links (mint + claim + landing page + deep link) ([#1668](https://github.com/block/buzz/pull/1668)) ([`2e529aab7`](https://github.com/block/buzz/commit/2e529aab759a18c1bb81e447f3696fe99db53a27)) diff --git a/crates/buzz-relay/Cargo.toml b/crates/buzz-relay/Cargo.toml index cbad2a3b296..65fe32b6b31 100644 --- a/crates/buzz-relay/Cargo.toml +++ b/crates/buzz-relay/Cargo.toml @@ -4,7 +4,7 @@ default-run = "buzz-relay" # Independent version: buzz-relay ships as a pinnable artifact # (ghcr.io/block/buzz), released on its own cadence via `just release-relay`. # It does NOT inherit the workspace version. -version = "0.2.0" +version = "0.2.1" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/desktop/package.json b/desktop/package.json index 7cfef35e260..14c412ff1de 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,7 +1,7 @@ { "name": "buzz", "private": true, - "version": "0.5.7", + "version": "0.5.8", "type": "module", "scripts": { "dev": "vite", diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index ad9c242eb11..5e43492d1fd 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -226,9 +226,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "approx" @@ -1064,7 +1064,7 @@ dependencies = [ [[package]] name = "buzz-desktop" -version = "0.5.7" +version = "0.5.8" dependencies = [ "anyhow", "arboard", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index b8f58568b47..9b2de6a575f 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -7,7 +7,7 @@ members = ["crates/buzz-terminal"] [package] name = "buzz-desktop" -version = "0.5.7" +version = "0.5.8" description = "Buzz desktop app" authors = ["you"] edition = "2021" diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 1b0a63e293b..f4b82575bc0 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "BitcoinMarkets", - "version": "0.5.7", + "version": "0.5.8", "identifier": "app.bitcoinmarkets.desktop", "build": { "beforeDevCommand": { diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx index 29bfd7dbab5..d24c8fab79f 100644 --- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx +++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx @@ -747,7 +747,7 @@ function PromptContextDialog({ return ( -
+
Prompt context {setupText ? ( diff --git a/desktop/src/features/agents/ui/PromptSectionAccordion.tsx b/desktop/src/features/agents/ui/PromptSectionAccordion.tsx index 0b5e58cc6c7..ad5346db157 100644 --- a/desktop/src/features/agents/ui/PromptSectionAccordion.tsx +++ b/desktop/src/features/agents/ui/PromptSectionAccordion.tsx @@ -47,7 +47,7 @@ export function PromptSectionAccordion({
{section.title} @@ -56,8 +56,8 @@ export function PromptSectionAccordion({ className={cn( "mt-1 text-xs leading-5 text-foreground/70", open - ? "whitespace-pre-wrap wrap-break-word" - : "line-clamp-2 wrap-break-word", + ? "whitespace-pre-wrap wrap-anywhere" + : "line-clamp-2 wrap-anywhere", )} > {body.length > 0 ? ( diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 7ea63f23396..410b05a2ccd 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -43,12 +43,8 @@ import { ChannelComposerActivityAccessory } from "@/features/channels/ui/Channel import { containsWelcomePersonaMention, WelcomeComposerGuidanceLayer, - WELCOME_COMPOSER_BANNER_DISMISS_DURATION_SECONDS, - WELCOME_COMPOSER_BANNER_HIDE_BUFFER_MS, - WELCOME_COMPOSER_BANNER_SUCCESS_SETTLE_MS, - WELCOME_PERSONA_ROTATION_MS, - type WelcomeComposerBannerState, } from "@/features/channels/ui/WelcomeComposerBanner"; +import { useWelcomeComposerBanner } from "@/features/channels/ui/useWelcomeComposerBanner"; import { mentionsKnownAgent } from "@/features/channels/ui/ChannelPane.helpers"; import { HuddleStartingView, HuddleTranscriptIntro } from "@/features/huddle"; import { useChannelIntro } from "@/features/channels/ui/useChannelIntro"; @@ -168,11 +164,6 @@ export const ChannelPane = React.memo(function ChannelPane({ const timelineScrollRef = React.useRef(null); const messageTimelineRef = React.useRef(null); const composerWrapperRef = React.useRef(null); - const completedWelcomeBannerChannelIdsRef = React.useRef(new Set()); - const welcomeComposerDismissTimerRef = React.useRef(null); - const welcomeComposerHideTimerRef = React.useRef(null); - const [welcomeComposerBannerState, setWelcomeComposerBannerState] = - React.useState("prompt"); const { goChannel } = useAppNavigation(); const prepareDmSendChannel = usePrepareDmSendChannel( activeChannel, @@ -221,36 +212,15 @@ export const ChannelPane = React.memo(function ChannelPane({ "css-variable", () => messageTimelineRef.current?.settleAtBottom() ?? false, ); - const clearWelcomeComposerDismissTimer = React.useCallback(() => { - if (welcomeComposerDismissTimerRef.current !== null) { - window.clearTimeout(welcomeComposerDismissTimerRef.current); - welcomeComposerDismissTimerRef.current = null; - } - if (welcomeComposerHideTimerRef.current !== null) { - window.clearTimeout(welcomeComposerHideTimerRef.current); - welcomeComposerHideTimerRef.current = null; - } - }, []); - React.useEffect( - () => () => clearWelcomeComposerDismissTimer(), - [clearWelcomeComposerDismissTimer], - ); - React.useEffect(() => { - clearWelcomeComposerDismissTimer(); - if ( - activeChannelId && - isActiveWelcomeChannel && - completedWelcomeBannerChannelIdsRef.current.has(activeChannelId) - ) { - setWelcomeComposerBannerState("hidden"); - return; - } - setWelcomeComposerBannerState("prompt"); - }, [ + const { + bannerState: welcomeComposerBannerState, + completeBanner: completeWelcomeComposerBanner, + dismissBanner: handleDismissWelcomeBanner, + } = useWelcomeComposerBanner( activeChannelId, - clearWelcomeComposerDismissTimer, isActiveWelcomeChannel, - ]); + currentPubkey ?? null, + ); const isEditInThread = editTarget != null && threadHeadMessage != null && @@ -330,31 +300,6 @@ export const ChannelPane = React.memo(function ChannelPane({ return pubkeys; }, [activityAgents, agentPubkeys, agentSessionAgents]); - const completeWelcomeComposerBanner = React.useCallback(() => { - if (!activeChannelId || !isActiveWelcomeChannel) { - return; - } - - clearWelcomeComposerDismissTimer(); - completedWelcomeBannerChannelIdsRef.current.add(activeChannelId); - setWelcomeComposerBannerState("complete"); - welcomeComposerDismissTimerRef.current = window.setTimeout(() => { - setWelcomeComposerBannerState("dismissing"); - welcomeComposerDismissTimerRef.current = null; - welcomeComposerHideTimerRef.current = window.setTimeout( - () => { - setWelcomeComposerBannerState("hidden"); - welcomeComposerHideTimerRef.current = null; - }, - WELCOME_COMPOSER_BANNER_DISMISS_DURATION_SECONDS * 1000 + - WELCOME_COMPOSER_BANNER_HIDE_BUFFER_MS, - ); - }, WELCOME_PERSONA_ROTATION_MS + WELCOME_COMPOSER_BANNER_SUCCESS_SETTLE_MS); - }, [ - activeChannelId, - clearWelcomeComposerDismissTimer, - isActiveWelcomeChannel, - ]); const handleSendMessage = React.useCallback( async ( content: string, @@ -739,6 +684,7 @@ export const ChannelPane = React.memo(function ChannelPane({ > {isActiveWelcomeChannel && !timeoutState.active ? ( diff --git a/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx b/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx index f75b8dbeb96..9a8db082ed2 100644 --- a/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx +++ b/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; -import { Bot, Check } from "lucide-react"; +import { Bot, Check, X } from "lucide-react"; import { ComposerDockGlassBackdrop } from "@/features/messages/ui/ComposerDockBackdrop"; import { cn } from "@/shared/lib/cn"; @@ -291,9 +291,15 @@ type WelcomeComposerBannerProps = { * banner during this window. */ settingUp?: boolean; + /** + * Called when the user dismisses the banner manually via the close button. + * Only rendered while `state === "prompt"`. + */ + onDismiss?: () => void; }; export function WelcomeComposerBanner({ + onDismiss, settingUp = false, state, }: WelcomeComposerBannerProps) { @@ -307,7 +313,7 @@ export function WelcomeComposerBanner({ animate={{ height: state === "dismissing" ? 0 : "auto", }} - className="overflow-visible" + className="overflow-hidden" initial={false} transition={{ duration: @@ -326,7 +332,7 @@ export function WelcomeComposerBanner({ : 0, }} className={cn( - "relative z-[1] mx-5 -mb-3 flex items-center gap-2 rounded-t-2xl border border-b-0 px-4 pb-5 pt-2.5 text-sm leading-5 transition-colors", + "relative z-[1] mx-5 mb-0 flex items-center gap-2 rounded-t-2xl border border-b-0 px-4 pb-5 pt-2.5 text-sm leading-5 transition-colors", state !== "prompt" ? "border-emerald-500/30 bg-emerald-500/15 text-foreground" : "border-border/60 bg-muted/55 text-muted-foreground", @@ -376,7 +382,7 @@ export function WelcomeComposerBanner({ {state !== "prompt" ? ( )} + {state === "prompt" && onDismiss && !settingUp ? ( + + ) : null} @@ -422,22 +439,26 @@ type WelcomeComposerGuidanceLayerProps = WelcomeComposerBannerProps & { export function WelcomeComposerGuidanceLayer({ children, + onDismiss, settingUp, state, }: WelcomeComposerGuidanceLayerProps) { + if (state === "hidden") { + return null; + } + return ( -
-
- - {children} - -
+
+ + {children} +
); } diff --git a/desktop/src/features/channels/ui/useWelcomeComposerBanner.ts b/desktop/src/features/channels/ui/useWelcomeComposerBanner.ts new file mode 100644 index 00000000000..541d8c10dc6 --- /dev/null +++ b/desktop/src/features/channels/ui/useWelcomeComposerBanner.ts @@ -0,0 +1,114 @@ +import * as React from "react"; + +import { + WELCOME_COMPOSER_BANNER_DISMISS_DURATION_SECONDS, + WELCOME_COMPOSER_BANNER_HIDE_BUFFER_MS, + WELCOME_COMPOSER_BANNER_SUCCESS_SETTLE_MS, + WELCOME_PERSONA_ROTATION_MS, + type WelcomeComposerBannerState, +} from "@/features/channels/ui/WelcomeComposerBanner"; + +const completedWelcomeComposerIdentityPubkeys = new Set(); + +/** + * Manages the Welcome-channel composer hint banner's state machine. + * + * Remembers completion across the Welcome experience per identity for this app + * session, so the hint stays hidden while moving between the private and + * starter Welcome channels without leaking dismissal to another identity. + * - `completeBanner`: agent-mention path — plays the "Nice work." success + * animation before auto-dismissing. + * - `dismissBanner`: manual X-button path — immediately begins the slide-down + * dismiss animation. + */ +export function useWelcomeComposerBanner( + activeChannelId: string | null, + isActiveWelcomeChannel: boolean, + identityPubkey: string | null, +): { + bannerState: WelcomeComposerBannerState; + completeBanner: () => void; + dismissBanner: () => void; +} { + const dismissTimerRef = React.useRef(null); + const hideTimerRef = React.useRef(null); + const [bannerState, setBannerState] = + React.useState("prompt"); + + const clearTimers = React.useCallback(() => { + if (dismissTimerRef.current !== null) { + window.clearTimeout(dismissTimerRef.current); + dismissTimerRef.current = null; + } + if (hideTimerRef.current !== null) { + window.clearTimeout(hideTimerRef.current); + hideTimerRef.current = null; + } + }, []); + + React.useEffect(() => () => clearTimers(), [clearTimers]); + + React.useEffect(() => { + clearTimers(); + if ( + isActiveWelcomeChannel && + identityPubkey && + completedWelcomeComposerIdentityPubkeys.has(identityPubkey) + ) { + setBannerState("hidden"); + return; + } + setBannerState("prompt"); + }, [clearTimers, identityPubkey, isActiveWelcomeChannel]); + + const scheduleHide = React.useCallback(() => { + hideTimerRef.current = window.setTimeout( + () => { + setBannerState("hidden"); + hideTimerRef.current = null; + }, + WELCOME_COMPOSER_BANNER_DISMISS_DURATION_SECONDS * 1000 + + WELCOME_COMPOSER_BANNER_HIDE_BUFFER_MS, + ); + }, []); + + const completeBanner = React.useCallback(() => { + if (!activeChannelId || !isActiveWelcomeChannel || !identityPubkey) { + return; + } + + clearTimers(); + completedWelcomeComposerIdentityPubkeys.add(identityPubkey); + setBannerState("complete"); + dismissTimerRef.current = window.setTimeout(() => { + setBannerState("dismissing"); + dismissTimerRef.current = null; + scheduleHide(); + }, WELCOME_PERSONA_ROTATION_MS + WELCOME_COMPOSER_BANNER_SUCCESS_SETTLE_MS); + }, [ + activeChannelId, + clearTimers, + identityPubkey, + isActiveWelcomeChannel, + scheduleHide, + ]); + + const dismissBanner = React.useCallback(() => { + if (!activeChannelId || !isActiveWelcomeChannel || !identityPubkey) { + return; + } + + clearTimers(); + completedWelcomeComposerIdentityPubkeys.add(identityPubkey); + setBannerState("dismissing"); + scheduleHide(); + }, [ + activeChannelId, + clearTimers, + identityPubkey, + isActiveWelcomeChannel, + scheduleHide, + ]); + + return { bannerState, completeBanner, dismissBanner }; +} diff --git a/desktop/tests/e2e/onboarding.spec.ts b/desktop/tests/e2e/onboarding.spec.ts index e5217da3d35..403edbcda13 100644 --- a/desktop/tests/e2e/onboarding.spec.ts +++ b/desktop/tests/e2e/onboarding.spec.ts @@ -207,6 +207,7 @@ async function expectWelcomeComposerBannerLayout(page: Page) { .locator("div") .boundingBox(); const guidanceLayer = page.getByTestId("welcome-composer-guidance-layer"); + const guidanceLayerBox = await guidanceLayer.boundingBox(); const guidanceBackdrop = page.getByTestId( "welcome-composer-guidance-backdrop", ); @@ -217,6 +218,7 @@ async function expectWelcomeComposerBannerLayout(page: Page) { !personaMentionBox || !composerBox || !dockBackdropBox || + !guidanceLayerBox || !guidanceBackdropBox ) { throw new Error("Could not measure welcome composer banner layout"); @@ -226,23 +228,20 @@ async function expectWelcomeComposerBannerLayout(page: Page) { await composer.getByTestId("welcome-composer-guide-banner").count(), ).toBe(0); expect(bannerBox.y).toBeLessThan(composerBox.y); - expect(bannerBox.y + bannerBox.height).toBeGreaterThan(composerBox.y); - expect(Math.abs(dockBackdropBox.y - composerBox.y)).toBeLessThanOrEqual(1); + // Banner is in normal flow above the composer, no overlap. + expect(bannerBox.y + bannerBox.height).toBeLessThanOrEqual(composerBox.y); + // The dock backdrop is absolute inset-y-0 inside composer-dock, which now + // contains the guidance layer + composer in flow, so its top aligns with the + // guidance layer top (not the composer top). + expect(Math.abs(dockBackdropBox.y - guidanceLayerBox.y)).toBeLessThanOrEqual( + 1, + ); expect(guidanceBackdropBox.y).toBeLessThanOrEqual(bannerBox.y); - expect( - Math.abs( - guidanceBackdropBox.y + guidanceBackdropBox.height - composerBox.y, - ), - ).toBeLessThanOrEqual(1); - const [guidanceZIndex, backdropZIndex] = await Promise.all([ - guidanceLayer.evaluate((element) => - Number(window.getComputedStyle(element).zIndex), - ), - page - .getByTestId("composer-dock-backdrop") - .evaluate((element) => Number(window.getComputedStyle(element).zIndex)), - ]); - expect(guidanceZIndex).toBeLessThan(backdropZIndex); + // The guidance backdrop extends bottom-3 (12px) short of the banner's bottom, + // visually connecting up to the composer. + expect(guidanceBackdropBox.y + guidanceBackdropBox.height).toBeLessThan( + composerBox.y, + ); expect( await page .getByTestId("channel-composer-overlay") @@ -3170,6 +3169,61 @@ test("finishing onboarding creates starter channels and focuses welcome-everyone await expectWelcomeComposerBannerCompletesAfterPersonaMention(page); }); +test("welcome-everywhere banner: X dismiss removes the guidance surface", async ({ + page, +}) => { + await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); + await installMockBridge(page, undefined, { skipOnboardingSeed: true }); + await page.goto("/"); + + await page.getByTestId("onboarding-display-name").fill("Morty QA"); + await completeProfileOnboarding(page); + + const banner = page.getByTestId("welcome-composer-guide-banner"); + const guidanceLayer = page.getByTestId("welcome-composer-guidance-layer"); + const dismissButton = page.getByTestId("welcome-composer-dismiss-button"); + + // Banner and guidance layer are visible in the prompt state. + await expect(banner).toBeVisible(); + await expect(guidanceLayer).toBeVisible(); + await expect(dismissButton).toBeVisible(); + + await dismissButton.click(); + + // After dismiss the entire guidance surface must be gone. + await expect(banner).toHaveCount(0, { timeout: 2_000 }); + await expect(guidanceLayer).toHaveCount(0); +}); + +test("welcome-everywhere banner: dismiss persists after channel re-entry", async ({ + page, +}) => { + await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); + await installMockBridge(page, undefined, { skipOnboardingSeed: true }); + await page.goto("/"); + + await page.getByTestId("onboarding-display-name").fill("Morty QA"); + await completeProfileOnboarding(page); + + const banner = page.getByTestId("welcome-composer-guide-banner"); + + await expect(banner).toBeVisible(); + await page.getByTestId("welcome-composer-dismiss-button").click(); + await expect(banner).toHaveCount(0, { timeout: 2_000 }); + + // Leave the Welcome channel. + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toContainText("general"); + await expect(banner).toHaveCount(0); + + // Return — banner must stay hidden. + await page.getByTestId("channel-welcome-everyone").click(); + await expect(page.getByTestId("chat-title")).toContainText( + "welcome-everyone", + ); + await expect(banner).toHaveCount(0); +}); + test("initial profile read failures still hold incomplete users in onboarding", async ({ page, }) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2420f4f37f4..78a39628dc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: dependencies: '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 6.0.5(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) react: specifier: ^19.1.0 version: 19.2.8 @@ -47,13 +47,13 @@ importers: version: 6.9.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/react': specifier: ^19.1.8 - version: 19.2.17 + version: 19.2.18 '@types/react-dom': specifier: ^19.1.6 - version: 19.2.3(@types/react@19.2.17) + version: 19.2.4(@types/react@19.2.18) jsdom: specifier: ^27.3.0 version: 27.4.0(@noble/hashes@2.2.0) @@ -92,46 +92,46 @@ importers: version: 0.10.35 '@radix-ui/react-alert-dialog': specifier: ^1.1.15 - version: 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-avatar': specifier: ^1.1.11 - version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-checkbox': specifier: ^1.3.3 - version: 1.3.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.3.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-context-menu': specifier: ^2.2.16 - version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 2.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-dialog': specifier: ^1.1.15 - version: 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-dropdown-menu': specifier: ^2.1.16 - version: 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-focus-scope': specifier: ^1.1.8 - version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-popover': specifier: ^1.1.15 - version: 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-separator': specifier: ^1.1.8 - version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.3.3(@types/react@19.2.17)(react@19.2.8) + version: 1.3.3(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-switch': specifier: ^1.2.6 - version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-tabs': specifier: ^1.1.13 - version: 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.21(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-toggle': specifier: ^1.1.10 - version: 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-tooltip': specifier: ^1.2.8 - version: 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-query': specifier: ^5.90.21 version: 5.100.14(react@19.2.8) @@ -140,7 +140,7 @@ importers: version: 1.170.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-virtual': specifier: ^3.14.2 - version: 3.14.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tauri-apps/api': specifier: ~2.11 version: 2.11.0 @@ -170,7 +170,7 @@ importers: version: 3.22.5 '@tiptap/react': specifier: ^3.22.3 - version: 3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tiptap/starter-kit': specifier: ^3.22.3 version: 3.22.5 @@ -212,7 +212,7 @@ importers: version: 19.2.8(react@19.2.8) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.17)(react@19.2.8) + version: 10.1.0(@types/react@19.2.18)(react@19.2.8) remark-breaks: specifier: ^4.0.0 version: 4.0.0 @@ -264,16 +264,16 @@ importers: version: 2.11.4 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@types/react': specifier: ^19.1.8 - version: 19.2.17 + version: 19.2.18 '@types/react-dom': specifier: ^19.1.6 - version: 19.2.3(@types/react@19.2.17) + version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 6.0.5(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) jsdom: specifier: ^27.4.0 version: 27.4.0(@noble/hashes@2.2.0) @@ -282,7 +282,7 @@ importers: version: 2.23.12(typescript@6.0.3) postcss: specifier: ^8.5.8 - version: 8.5.19 + version: 8.5.26 tailwindcss: specifier: ^4.3.0 version: 4.3.0 @@ -303,13 +303,13 @@ importers: version: 5.2.8 '@isomorphic-git/lightning-fs': specifier: ^4.6.2 - version: 4.6.2 + version: 4.6.3 '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.3.3(@types/react@19.2.17)(react@19.2.8) + version: 1.3.3(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-tooltip': specifier: ^1.2.8 - version: 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.20(tailwindcss@4.3.0) @@ -330,7 +330,7 @@ importers: version: 2.1.1 isomorphic-git: specifier: ^1.38.3 - version: 1.38.7(patch_hash=e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f) + version: 1.38.10(patch_hash=e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f) lucide-react: specifier: ^1.0.0 version: 1.16.0(react@19.2.8) @@ -345,7 +345,7 @@ importers: version: 19.2.8(react@19.2.8) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.17)(react@19.2.8) + version: 10.1.0(@types/react@19.2.18)(react@19.2.8) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -370,16 +370,16 @@ importers: version: 1.162.0 '@types/react': specifier: ^19.1.8 - version: 19.2.17 + version: 19.2.18 '@types/react-dom': specifier: ^19.1.6 - version: 19.2.3(@types/react@19.2.17) + version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 6.0.5(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)) postcss: specifier: ^8.5.8 - version: 8.5.19 + version: 8.5.26 tailwindcss: specifier: ^4.3.0 version: 4.3.0 @@ -675,8 +675,8 @@ packages: '@isomorphic-git/idb-keyval@3.3.2': resolution: {integrity: sha512-r8/AdpiS0/WJCNR/t/gsgL+M8NMVj/ek7s60uz3LmpCaTF2mEVlZJlB01ZzalgYzRLXwSPC92o+pdzjM7PN/pA==} - '@isomorphic-git/lightning-fs@4.6.2': - resolution: {integrity: sha512-RS/oa1UBnoUFe56bsjOEgoUUReYKQzYUlQnbERRRNv9s9KmjyWuuylPV+YgsWirR2oONKaipWYMebVQ8SAe55Q==} + '@isomorphic-git/lightning-fs@4.6.3': + resolution: {integrity: sha512-uIXSSqutetjk+qHlswb5aKTzw7zOJ851R6r53szkkprtZ8Wp4A8gPNf7meeKS7uLDJaFvKhFTY7KISh3ULRojg==} hasBin: true '@jridgewell/gen-mapping@0.3.13': @@ -1720,8 +1720,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-virtual@3.14.8': - resolution: {integrity: sha512-O39GJQpAYEJcIu3uN1//YtmhjSEOyw75vg9CKCatBDPiD5hKtZQoJHfferyrB/LdOD3UWaoMLWtdEjarwIwdDw==} + '@tanstack/react-virtual@3.14.9': + resolution: {integrity: sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1762,8 +1762,8 @@ packages: '@tanstack/store@0.9.3': resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} - '@tanstack/virtual-core@3.17.6': - resolution: {integrity: sha512-h0/Ebo18CkOrChlQIhNtQkM5ySUnh/GumQ/D1st3hG2HWUPEF+ILUc2k29UtivCi/9G7w7G3/f7Xyd5cCFbKBw==} + '@tanstack/virtual-core@3.17.7': + resolution: {integrity: sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==} '@tanstack/virtual-file-routes@1.162.0': resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} @@ -2088,13 +2088,13 @@ packages: '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2109,8 +2109,8 @@ packages: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher - '@vitejs/plugin-react@6.0.3': - resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} + '@vitejs/plugin-react@6.0.5': + resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -2650,8 +2650,8 @@ packages: resolution: {integrity: sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==} engines: {node: '>=18'} - isomorphic-git@1.38.7: - resolution: {integrity: sha512-2J2rCV7gTgcpscEDT7PjU5shbsKKI10E4RqBlPDI3LmMXDjSKsN2w1jTIKiay14v87sPDfXjJkgp/Rrxc0zlSg==} + isomorphic-git@1.38.10: + resolution: {integrity: sha512-nJSSq7ypu97vM33rSdxXyPzSWksCberjKspzXhFEcBHdVyxdNkWByAzJ/AURV1jIlfv8pLq37lGdIEt7ORj17Q==} engines: {node: '>=14.17'} hasBin: true @@ -3002,6 +3002,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + node-releases@2.0.46: resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} engines: {node: '>=18'} @@ -3102,6 +3107,10 @@ packages: resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + prettier@3.8.3: resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} @@ -4004,7 +4013,7 @@ snapshots: '@isomorphic-git/idb-keyval@3.3.2': {} - '@isomorphic-git/lightning-fs@4.6.2': + '@isomorphic-git/lightning-fs@4.6.3': dependencies: '@isomorphic-git/idb-keyval': 3.3.2 isomorphic-textencoder: 1.0.1 @@ -4059,677 +4068,677 @@ snapshots: '@radix-ui/primitive@1.1.7': {} - '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-avatar@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-avatar@1.1.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-context@1.1.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-context@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-context@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-context@1.2.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-direction@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-direction@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-id@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-id@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) aria-hidden: 1.2.6 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.8) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) '@radix-ui/rect': 1.1.1 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.18)(react@19.2.8) '@radix-ui/rect': 1.1.3 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-primitive@2.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-slot': 1.2.5(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-slot@1.2.5(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-slot@1.2.5(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-slot@1.3.3(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: '@radix-ui/rect': 1.1.3 react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-use-size@1.1.4(@types/react@19.2.17)(react@19.2.8)': + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.8) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) '@radix-ui/rect@1.1.1': {} @@ -4907,7 +4916,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 - postcss: 8.5.19 + postcss: 8.5.26 tailwindcss: 4.3.0 '@tailwindcss/typography@0.5.20(tailwindcss@4.3.0)': @@ -4940,9 +4949,9 @@ snapshots: react-dom: 19.2.8(react@19.2.8) use-sync-external-store: 1.6.0(react@19.2.8) - '@tanstack/react-virtual@3.14.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-virtual@3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@tanstack/virtual-core': 3.17.6 + '@tanstack/virtual-core': 3.17.7 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -5003,7 +5012,7 @@ snapshots: '@tanstack/store@0.9.3': {} - '@tanstack/virtual-core@3.17.6': {} + '@tanstack/virtual-core@3.17.7': {} '@tanstack/virtual-file-routes@1.162.0': {} @@ -5092,15 +5101,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 '@testing-library/dom': 10.4.1 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) '@tiptap/core@3.22.5(@tiptap/pm@3.22.5)': dependencies: @@ -5233,12 +5242,12 @@ snapshots: prosemirror-transform: 1.12.0 prosemirror-view: 1.41.8 - '@tiptap/react@3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tiptap/react@3.22.5(@floating-ui/dom@1.8.0)(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@tiptap/core': 3.22.5(@tiptap/pm@3.22.5) '@tiptap/pm': 3.22.5 - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) '@types/use-sync-external-store': 0.0.6 fast-equals: 5.4.0 react: 19.2.8 @@ -5333,11 +5342,11 @@ snapshots: dependencies: undici-types: 7.19.2 - '@types/react-dom@19.2.3(@types/react@19.2.17)': + '@types/react-dom@19.2.4(@types/react@19.2.18)': dependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@types/react@19.2.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 @@ -5349,7 +5358,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@6.0.3(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.5(vite@8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0) @@ -5843,7 +5852,7 @@ snapshots: isbot@5.1.40: {} - isomorphic-git@1.38.7(patch_hash=e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f): + isomorphic-git@1.38.10(patch_hash=e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f): dependencies: async-lock: 1.4.1 clean-git-ref: 2.0.1 @@ -6384,6 +6393,8 @@ snapshots: nanoid@3.3.16: {} + nanoid@3.3.17: {} + node-releases@2.0.46: {} nostr-tools@2.23.12(typescript@6.0.3): @@ -6477,6 +6488,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.26: + dependencies: + nanoid: 3.3.17 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prettier@3.8.3: {} pretty-format@27.5.1: @@ -6595,11 +6612,11 @@ snapshots: react-is@17.0.2: {} - react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.8): + react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.2.17 + '@types/react': 19.2.18 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -6613,32 +6630,32 @@ snapshots: transitivePeerDependencies: - supports-color - react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.8): + react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8): dependencies: react: 19.2.8 - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.8) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.8): + react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.8): dependencies: react: 19.2.8 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.8) - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.8) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.8) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.8) - use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.8) + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.8) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.8): + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): dependencies: get-nonce: 1.0.1 react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 react@19.2.8: {} @@ -6957,20 +6974,20 @@ snapshots: dependencies: pako: 1.0.11 - use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.8): + use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8): dependencies: react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.8): + use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8): dependencies: detect-node-es: 1.1.0 react: 19.2.8 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 use-sync-external-store@1.6.0(react@19.2.8): dependencies: