From a29ee94d84186f8a8eec9ec66eb8e7e9ac4953d2 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 17 Sep 2026 23:44:47 -0500 Subject: [PATCH] Port upstream UI polish and refresh dependencies Bring over selected Warp UI improvements: selectable Thought text, message timestamps, animated theme backgrounds, editable New Window shortcuts, safer tab renaming and restoration, and text/font/discard crash fixes. Match upstream orchestration pill geometry and tooltips, and replace expanded subagent previews with compact links that retain live status and conversation navigation for launches and follow-up messages. Include the pending Cargo.lock dependency refresh. Adapted upstream PRs: #15948, #15884, #15691, #14618, #15772, #15705, #15605, #14895, #15996, #15771, #14893, and #14603. --- Cargo.lock | 1451 ++++++++--------- app/src/ai/active_agent_views_model.rs | 11 + app/src/ai/blocklist/agent_view/mod.rs | 2 +- .../agent_view/orchestration_pill_bar.rs | 317 ++-- .../agent_view/subagent_inline_panel.rs | 391 ----- .../agent_view/subagent_inline_panel_tests.rs | 128 -- .../ai/blocklist/agent_view/subagent_link.rs | 110 ++ app/src/ai/blocklist/block.rs | 56 +- app/src/ai/blocklist/block/model.rs | 5 +- .../ai/blocklist/block/model/model_impl.rs | 10 +- app/src/ai/blocklist/block/view_impl.rs | 4 + .../block/view_impl/orchestration.rs | 80 +- .../block/view_impl/orchestration_tests.rs | 24 +- .../ai/blocklist/block/view_impl/output.rs | 3 +- app/src/ai/blocklist/block/view_impl/query.rs | 32 +- app/src/ai/blocklist/history_model.rs | 20 + app/src/ai/blocklist/history_model_tests.rs | 81 + app/src/ai/chatgpt_auth.rs | 7 + app/src/app_menus.rs | 12 +- app/src/code_review/code_review_view.rs | 20 +- app/src/code_review/code_review_view_tests.rs | 18 + app/src/pane_group/mod.rs | 36 +- app/src/pane_group/mod_tests.rs | 96 ++ app/src/pane_group/pane/terminal_pane.rs | 6 +- app/src/resource_center/utils.rs | 5 - app/src/settings_view/about_page.rs | 7 +- app/src/settings_view/mod.rs | 2 +- app/src/terminal/view.rs | 30 +- .../view/ambient_agent/block/query.rs | 2 + app/src/terminal/view/context_menu.rs | 21 + app/src/terminal/view_tests.rs | 74 +- app/src/ui_components/icon_with_status.rs | 14 +- app/src/util/bindings.rs | 1 - app/src/util/time_format.rs | 8 + app/src/util/time_format_tests.rs | 21 + app/src/workspace/mod.rs | 26 +- app/src/workspace/view.rs | 28 +- app/src/workspace/view_tests.rs | 56 +- .../src/elements/event_handler.rs | 70 +- .../src/elements/formatted_text_element.rs | 4 + .../elements/formatted_text_element_tests.rs | 80 +- .../src/elements/selectable_area.rs | 16 +- .../src/elements/selectable_area_tests.rs | 221 +++ crates/galaxyui_core/src/fonts.rs | 21 +- crates/galaxyui_core/src/fonts_tests.rs | 139 ++ crates/galaxyui_core/src/image_cache_tests.rs | 242 +++ 46 files changed, 2529 insertions(+), 1479 deletions(-) delete mode 100644 app/src/ai/blocklist/agent_view/subagent_inline_panel.rs delete mode 100644 app/src/ai/blocklist/agent_view/subagent_inline_panel_tests.rs create mode 100644 app/src/ai/blocklist/agent_view/subagent_link.rs create mode 100644 crates/galaxyui_core/src/elements/selectable_area_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 3fcc501e..96b3d6a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,10 +60,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.1", +] + [[package]] name = "aes-gcm" version = "0.10.3" @@ -71,8 +82,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "ghash", "subtle", @@ -85,8 +96,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "polyval", "subtle", @@ -100,8 +111,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e08d0cdb774acd1e4dac11478b1a0c0d203134b2aab0ba25eb430de9b18f8b9" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "cmac", "ctr", "dbl", @@ -123,7 +134,7 @@ dependencies = [ "futures", "futures-concurrency", "rustc-hash 2.1.3", - "rustix 1.1.4", + "rustix 1.1.5", "schemars 1.2.2", "serde", "serde_json", @@ -135,12 +146,12 @@ dependencies = [ [[package]] name = "agent-client-protocol-derive" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abd4080f51e4f24f5042beb7fb7a66ede29a2dc1c2582c329532e1c27264ddc" +checksum = "7e8d5f32208cdc11238004bec4e4229d68fb937598f900e153468ca084865fa5" dependencies = [ "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -154,7 +165,7 @@ dependencies = [ "schemars 1.2.2", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "strum 0.28.0", "tracing", ] @@ -171,21 +182,10 @@ dependencies = [ "mime", "nom 7.1.3", "pin-project-lite", - "reqwest 0.13.4", + "reqwest 0.13.5", "thiserror 1.0.69", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -338,7 +338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" dependencies = [ "alsa-sys", - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "libc", ] @@ -360,7 +360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", - "bitflags 2.13.1", + "bitflags 2.13.2", "cc", "jni 0.22.4", "libc", @@ -507,9 +507,9 @@ dependencies = [ [[package]] name = "arborium" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb7a99c146e42da48d5783335814d9bd0f088143aa51fb5ea5b59855df5ca70" +checksum = "230e826fe3204178030e420751396c99431e4611fc67af63baf7767b4317bd75" dependencies = [ "arborium-bash", "arborium-c", @@ -552,9 +552,9 @@ dependencies = [ [[package]] name = "arborium-bash" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa5ef1fa9c298d071d0817b068880a22c86f11202508215bdfddc3386aae643" +checksum = "ab2a2c191c27ee915e5ae65b619f9f7c1345565d511ce7761d30b4b243b2e333" dependencies = [ "arborium-sysroot", "cc", @@ -563,9 +563,9 @@ dependencies = [ [[package]] name = "arborium-c" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44b533ac7c14da8a26dfb8f7b4bfa29da969a7f01015d63a3ed4097af14c9d" +checksum = "37615879a3956f8918d43be52c9c4d11d60af5b7bedabca87ab8b47f769fa8c7" dependencies = [ "arborium-sysroot", "cc", @@ -574,9 +574,9 @@ dependencies = [ [[package]] name = "arborium-c-sharp" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74247ec493c8d377fe9b905c33650742ffd6a3bce87849df656bbb34ec4e3c78" +checksum = "1da8f9108bf98f334190243c27080476b126c54981fe2cb9b04d4f25d092960e" dependencies = [ "arborium-sysroot", "cc", @@ -585,9 +585,9 @@ dependencies = [ [[package]] name = "arborium-cpp" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f307d299c2089784e11c6a93374b80c0d5f42c46bdc9f10bcd02163a1fd30bb" +checksum = "736c482444d8d58e7f3cd8df7bc88bf6c4b450e9039a55c43b3aec39d6a805ce" dependencies = [ "arborium-c", "arborium-sysroot", @@ -597,9 +597,9 @@ dependencies = [ [[package]] name = "arborium-css" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "040085bd355e439915eaeca9ed9db316a8dc3795f36c23948f216a808192376f" +checksum = "d3bf02fc773bafaac2cdc27862d6eb52e86927e740c69e0d87b4123f2589b9af" dependencies = [ "arborium-sysroot", "cc", @@ -608,9 +608,9 @@ dependencies = [ [[package]] name = "arborium-dockerfile" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168968cacf937c51bb8ed785780467547a5158141d2c53c0d8f6fb64a60d86c9" +checksum = "54f567b2f106cea694126516b8ad20d44cddc40df8c16c77acf61ada303bfd9e" dependencies = [ "arborium-sysroot", "cc", @@ -619,9 +619,9 @@ dependencies = [ [[package]] name = "arborium-elixir" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5523d0749ec7f0b020cab4e0a37d64f5bf52096b6851cca516f0c07941fbf256" +checksum = "ad30809acaa5d56cc1a660cf9684884d37f86f994d35d6238ba2e1eaf9b409c1" dependencies = [ "arborium-sysroot", "cc", @@ -630,9 +630,9 @@ dependencies = [ [[package]] name = "arborium-go" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9a45c7deab55b1dfed13074fe9cbc0bfa8d4a40e4bc509f4295291e53efdec" +checksum = "f297634bca52a796de2725149dc684c18286c78f0a096fa27d532f9627bb1e02" dependencies = [ "arborium-sysroot", "cc", @@ -641,9 +641,9 @@ dependencies = [ [[package]] name = "arborium-hcl" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b1c28c0abb1030dc2f82b9b1f51bbec933571375261bddccae8c4a996e8231" +checksum = "30dc0e04b767c8345c41cc96b350e0e991826ff340d973bd52b1f27fc7963a12" dependencies = [ "arborium-sysroot", "cc", @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "arborium-highlight" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65635c663883cb887d52abce06650f81e4abeb7bfd8e79a555f6a0d8e8f0a31b" +checksum = "cee400d694bd935c513c50f9b1fda469b6d9cd192d80fede7bfa9e5851a54cb8" dependencies = [ "arborium-theme", "arborium-tree-sitter", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "arborium-html" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b109985d29a842c1fe328b567ac48f8d2211bce778dfe12da6297dd58adac2a" +checksum = "6ed785a76d53a63ff144b26a68b789fe4d10169a5f683d573ee72f3234d8a9a4" dependencies = [ "arborium-css", "arborium-javascript", @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "arborium-java" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e14774819787b842be24f7979f527f4a1cf8a60a231a66aef9bb8e31811ae9" +checksum = "ad9a5d6a278548b7a18bbaf79600402c167cec39d3b63be86daa1f3756b52a82" dependencies = [ "arborium-sysroot", "cc", @@ -687,9 +687,9 @@ dependencies = [ [[package]] name = "arborium-javascript" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424c648bf3075e137a5620318a7f865cf320c5f43163d3094764d4cc050e7cc0" +checksum = "47b63db8c449ac227061f2bb4ed62673d85c3f567be8f01483b34f266298d061" dependencies = [ "arborium-jsdoc", "arborium-sysroot", @@ -699,9 +699,9 @@ dependencies = [ [[package]] name = "arborium-jq" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f738b957b80adb9b479d5edd3afd86658337a06fd4b4d998a6856c9cf818678b" +checksum = "befae6cef2501d589f31ca08ab96fa640f57299eb05bfddf2ebcc772c1632a20" dependencies = [ "arborium-sysroot", "cc", @@ -710,9 +710,9 @@ dependencies = [ [[package]] name = "arborium-jsdoc" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f06a77e65f79ded95575bbc9b48a3f213e82ba1a23e5bc64ee5df77f14166cf4" +checksum = "73945c45bdbbed8c137556d3a833999463e707078ca877597756dc394689e8b5" dependencies = [ "arborium-sysroot", "cc", @@ -721,9 +721,9 @@ dependencies = [ [[package]] name = "arborium-json" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbd847461cd81d50cd34454870359bc73ac1b96512eb8a33943fe54cba82d9f" +checksum = "dd1167c5abde6c8d953cc20d88713006c94a009926d39e2217c68cbffbdbf7f0" dependencies = [ "arborium-sysroot", "cc", @@ -732,9 +732,9 @@ dependencies = [ [[package]] name = "arborium-kotlin" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39795ff23c1d15c870e4173d580f4188f78814ab919b9b01566a422426b9b697" +checksum = "d2be9de4405957a7e6f9684d4774a8e743bb501ce5956d3e91da77e8e18a6fe6" dependencies = [ "arborium-sysroot", "cc", @@ -743,9 +743,9 @@ dependencies = [ [[package]] name = "arborium-lua" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672db35847456504a89c9882092c04b659d9c65e92272dd4383a15f4cddcc57b" +checksum = "d5ffae9459733aa8457f88e43672ccae485a77f09b15da83f8655d954c75365f" dependencies = [ "arborium-sysroot", "cc", @@ -754,9 +754,9 @@ dependencies = [ [[package]] name = "arborium-nix" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56328852b01793707818ed971ae42129693ad0026d330a0fd42a600a54bd7aa" +checksum = "8c89b161cf29773dff8abc46e0bed263ecd3195f7a4f17c9b1bc0903ec72602f" dependencies = [ "arborium-sysroot", "cc", @@ -765,9 +765,9 @@ dependencies = [ [[package]] name = "arborium-objc" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd492b0f4a5478a5799c2b9b81c72e1be909ded9136b442a95e85f9f1f987c8c" +checksum = "b1988635123d7a6947c4596610c52dab1a8319e791517a8c61e0c0e5e0463f45" dependencies = [ "arborium-c", "arborium-sysroot", @@ -777,9 +777,9 @@ dependencies = [ [[package]] name = "arborium-php" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570bbca1ecf73c659650db6d7be73bdf996691055afc63f5c4eb1ef3e9a52d05" +checksum = "1772da0cd2f525332a958f5ebda27eda35be22dfa82b052774b6162b2c20b359" dependencies = [ "arborium-sysroot", "cc", @@ -788,9 +788,9 @@ dependencies = [ [[package]] name = "arborium-powershell" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e9c67ce414f1c898f6999b02226419ee81c11757c86045b9cc850930b7f83c" +checksum = "33e087e691371a5b63c514c7a5c85c213932be5c52ada6107c5fb2314106be07" dependencies = [ "arborium-sysroot", "cc", @@ -799,9 +799,9 @@ dependencies = [ [[package]] name = "arborium-python" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ade74ad0c312807b2f4c439c7e416c6de41f5a6a638cdea1d5e93470d87ba91" +checksum = "4da0da209e87d12f5922c5a7c005883d48d06baf0bf9b5d05bbe607b532e599f" dependencies = [ "arborium-sysroot", "cc", @@ -810,9 +810,9 @@ dependencies = [ [[package]] name = "arborium-ruby" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ff559db56a1a46e34d289e1076a89c4babc75fd06a9c30cfe54ab63307088c" +checksum = "9f791dc0377b8df659b98aed534dddfd4dcb591735cafd05b12e1e1ce3a859a9" dependencies = [ "arborium-sysroot", "cc", @@ -821,9 +821,9 @@ dependencies = [ [[package]] name = "arborium-rust" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a291f443c124244ff7dea2248a1f2bf60a3b4d6ab0585784ea680dc33fd7cd47" +checksum = "8471397c67c4164b93d6a23ae80fb5b7af5dcc00fa9b143015efb1ea927ca8da" dependencies = [ "arborium-sysroot", "cc", @@ -832,9 +832,9 @@ dependencies = [ [[package]] name = "arborium-scala" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cfeb6d01db8a42af104161a2ec7e2a8585072689195bdc5d3511929ce465ae" +checksum = "c02e14b416b76db7ede4935d5306e9ba5d42cd931396fb7ffc791de44ab46005" dependencies = [ "arborium-sysroot", "cc", @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "arborium-scss" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e63936c6eddc6fa79a9106c0910b52069ec9d74eba5e8234acc4374c2ab733c" +checksum = "c5baa8a1fe94fdf3a32859d34fb5b0283bcc521cc08a62ca006c42d3dfad5780" dependencies = [ "arborium-css", "arborium-sysroot", @@ -855,9 +855,9 @@ dependencies = [ [[package]] name = "arborium-sql" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f790fcca922217312f9e4c03d7423d74e37d4c5eec41a0b774019debd73be8" +checksum = "67ca021dab5e61d6eb07bad9cf40f5f5e0fb4c6f89f32aa14a1db07431d657f9" dependencies = [ "arborium-sysroot", "cc", @@ -866,9 +866,9 @@ dependencies = [ [[package]] name = "arborium-starlark" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f562d7734124b4894babd2450d3709668ddf6309415ed405c6e2565c2188d0" +checksum = "0101c83d354421235ff3de1a5c593d56314cc41d47e93755404a2ac721ecb0fd" dependencies = [ "arborium-sysroot", "cc", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "arborium-swift" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8251ab4f0af5b08e0439fb02e7e4e229cb814e75d145cbe26e0afabacfba3863" +checksum = "db83273e075d168a853cb480805b0614803d43c20c7d5fc3f655dac836dd88e1" dependencies = [ "arborium-sysroot", "cc", @@ -888,9 +888,9 @@ dependencies = [ [[package]] name = "arborium-sysroot" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d99d80550b726f9dec7ee6d07118c31e08b10e729ac488eabd4c10603dc841" +checksum = "ddd9d120dd5df8b918cd96676a88f1233a66f10687d415b69f70b3396acddddf" dependencies = [ "cc", "dlmalloc", @@ -898,15 +898,15 @@ dependencies = [ [[package]] name = "arborium-theme" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00a8f02b994f454d9703dd6a8b6075ba778320550d699198faff1882594c0f7" +checksum = "042f9d025c80b2e625b4f0f10eda56a652444ef916025e855a292937bb8a949a" [[package]] name = "arborium-toml" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102a80466c8afd64c0c05c57cae30e5ff5ddbf3f943065fdd8fbe64331b49952" +checksum = "a9f6144d675fbfe2e510c5886955581b6838eb613a3d1af4250ddf10de5bfa5d" dependencies = [ "arborium-sysroot", "cc", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "arborium-tree-sitter" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27f3bd6bb7192e4a19dc177748532b305e9052839b34e55705f53e5eea2d5ca" +checksum = "bd8de97695c76508ab780e03bd1db009f5db8567c985f5da5ae91a2ad57ef2bc" dependencies = [ "arborium-sysroot", "cc", @@ -929,9 +929,9 @@ dependencies = [ [[package]] name = "arborium-tsx" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459b71b663c89d3e03beb0226bdd1faac2ae5d5b36dbfcddbe4c631fd062b19e" +checksum = "d3533e40fe9f843290d4ff481c67ed1494595395fb60318dbc1da7e58bba42bb" dependencies = [ "arborium-javascript", "arborium-sysroot", @@ -941,9 +941,9 @@ dependencies = [ [[package]] name = "arborium-typescript" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a226449c2081a153662af88ac5db08d6ee2654ac9022bdc1bacb8a2769053d" +checksum = "0024689641490077765b291be46197f48d23e75cbade86768b1abed64d406ed1" dependencies = [ "arborium-javascript", "arborium-sysroot", @@ -953,9 +953,9 @@ dependencies = [ [[package]] name = "arborium-vue" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffce50016451cdbd77d6c7ab15ef96bc419c7842feff67e0fb27b19cef744749" +checksum = "91feaa2706ff071f7e9c516d09436e68a25ef8871772b009098c6182a2ea7afb" dependencies = [ "arborium-css", "arborium-html", @@ -969,9 +969,9 @@ dependencies = [ [[package]] name = "arborium-xml" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ab75e92a76a01cfbbc7720944c9436bbed931e7b3c5e397a94f088f05acc17" +checksum = "d01fb3f68edd5d901632cfbc9aa02ba537e16aafc3eda4603573acb2aeef7ae3" dependencies = [ "arborium-sysroot", "cc", @@ -980,9 +980,9 @@ dependencies = [ [[package]] name = "arborium-yaml" -version = "2.18.1" +version = "2.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83a224e870912490905c2cb21fa1d0b055ae9f88a47329f5849662cb9fe656f" +checksum = "528f9c28a2703384a66079b18f29e990f1ed749014a6556242e7c14ed5b29ea9" dependencies = [ "arborium-sysroot", "cc", @@ -1104,7 +1104,7 @@ dependencies = [ "futures", "galaxyui_core", "log", - "reqwest 0.13.4", + "reqwest 0.13.5", "url", ] @@ -1145,9 +1145,9 @@ dependencies = [ [[package]] name = "async-compat" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +checksum = "4c97d7ff3c25d6c10d64170c12acaf5d4245e76dece3779c1d92b153a64f11df" dependencies = [ "futures-core", "futures-io", @@ -1158,9 +1158,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.43" +version = "0.4.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd" dependencies = [ "compression-codecs", "compression-core", @@ -1206,7 +1206,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling", - "rustix 1.1.4", + "rustix 1.1.5", "slab", "windows-sys 0.61.2", ] @@ -1248,7 +1248,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite 2.6.1", - "rustix 1.1.4", + "rustix 1.1.5", ] [[package]] @@ -1274,7 +1274,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.4", + "rustix 1.1.5", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -1316,7 +1316,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -1443,9 +1443,9 @@ dependencies = [ [[package]] name = "aws-config" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" +checksum = "b8d7b388a9fc3a6db15a5ec778c38b354eff1364882c94d08e0252f7a47dcaa4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1492,9 +1492,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -1503,9 +1503,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" dependencies = [ "cc", "cmake", @@ -1516,9 +1516,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.1" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +checksum = "25b43ad47adc2517efe3d706559d94b97e50e80e0321b3cadbc9f77cee88adcd" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1542,9 +1542,9 @@ dependencies = [ [[package]] name = "aws-sdk-bedrock" -version = "1.153.0" +version = "1.157.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5175fd0be54231cc4f9d0a5cbb0a6384548fd6aa5c846c1d6945960d35d840" +checksum = "347e5d5ea47f43415003507fbd8aacecc8f7ccd09a2455f2b5e376921905e48f" dependencies = [ "arc-swap", "aws-credential-types", @@ -1568,9 +1568,9 @@ dependencies = [ [[package]] name = "aws-sdk-bedrockruntime" -version = "1.141.0" +version = "1.145.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6418b1f5feb3a8cc0fa10fb9af5693c88135ee03bae3ff48c8cdbf6a918daa07" +checksum = "aed4de4a98241f9dd95ddad4223755b7afc3327d0a148a2a29286c95ef64db5e" dependencies = [ "arc-swap", "aws-credential-types", @@ -1597,9 +1597,9 @@ dependencies = [ [[package]] name = "aws-sdk-signin" -version = "1.20.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5d0594dfe157911dbe9c988eefa935d2c885670fe6d712efe96d79d94ef184" +checksum = "d17ee48a8e3721e74125e2b29dcd9077a75d2cb6e44509444048fc4df1ce3b11" dependencies = [ "arc-swap", "aws-credential-types", @@ -1623,9 +1623,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.107.0" +version = "1.111.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0abd0f89cfe11da5099986dd493e4f94347ce9a4562cb86ddecfe926b6c0" +checksum = "65700d75f6da78c89a61a49f1539979400a4609d3329376b1dc2133d7a1b2b90" dependencies = [ "arc-swap", "aws-credential-types", @@ -1649,9 +1649,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.109.0" +version = "1.113.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4075b8a2c8cda4076a3dcc43b9d6dabd93e0c2502abeaaf7e14aaead9bb312b" +checksum = "eb1147c254af7b6cee3ace90ae7da6f21366ce9a9b899035fa8a0fdf586eb75a" dependencies = [ "arc-swap", "aws-credential-types", @@ -1675,9 +1675,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.112.0" +version = "1.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f582002918346a3e685be1b391c7bea155073088cea6bd4e4b7663df9e43b6c" +checksum = "035c7392e751d20bf1060d625d023587eb14099646110a9cff7c697bfe4b253a" dependencies = [ "arc-swap", "aws-credential-types", @@ -1702,9 +1702,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" +checksum = "31d955e76ff96acd555bf06fa0fa6d5bf9335fa84ae7c64481b20ae61d231f70" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -1736,9 +1736,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.61.2" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6de526c7b567420a31bc283657a7921b45c4cafe0827fdf2490713dcc770c28f" +checksum = "80c2051c2f1016fb8e6548dd07b8bc2ac9c3fe583721444b92f515e856d31609" dependencies = [ "aws-smithy-types", "bytes", @@ -1769,9 +1769,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfd138fac0337cee7516c352757ea73b9f2266e57d0bcb5bc70e9547e45aef1" +checksum = "7bd25384a4e437aa8d8f339afad4b69e786b936a7cb10db668a7aaf66717b1a8" dependencies = [ "aws-smithy-async", "aws-smithy-protocol-test", @@ -1784,7 +1784,7 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "indexmap 2.14.0", + "indexmap 2.14.2", "pin-project-lite", "rustls", "rustls-native-certs", @@ -1799,9 +1799,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.63.0" +version = "0.63.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" +checksum = "3385d469edbe8b60cc72002784652b5efca39178192aa9cc4b44c9875c6bdc18" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -1838,9 +1838,9 @@ dependencies = [ [[package]] name = "aws-smithy-query" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512346c7212ab7436df2d77a16d976a468ae44a418835511d2a69269810aaf62" +checksum = "f1d1d71f6562be974caa85442ecd90194c40fdb5df045f182a6c2e872ce95056" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.14.0" +version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" +checksum = "3296253d3a91b3f938a3f2bcce4daebadcb4aa4228153fcec90f9d23532b4484" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1877,9 +1877,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.15.0" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" +checksum = "6d881a7b7ad179fd6611680c9de89f716fb00ab40299a9a7b8c6913e8f7511a8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -1906,9 +1906,9 @@ dependencies = [ [[package]] name = "aws-smithy-schema" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" +checksum = "e8f395d93304280b64b7632fea798d177e74897fe7f063416ce627cd6fa24829" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -1917,9 +1917,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.6.2" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" +checksum = "0b791f3ac597193fe1d08b82366986eb1f5bc31f2ac6c194c0855276116c76cd" dependencies = [ "base64-simd", "bytes", @@ -1943,9 +1943,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce84f71c72fee2cbbadde6e7d082f5fb466e3a84733855295fa7aafd1b31b7d8" +checksum = "0b932c8d6dc127fc980eecd78f8694ae9b9551b69a93a7def2a199c1c0033daf" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -1955,9 +1955,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" +checksum = "209f3a6d82a6e9e5f94abbed94c7a26e1c052341002bf57a5fb5481f625896fc" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -2051,7 +2051,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.8.9", "object", "rustc-demangle", "windows-link 0.2.1", @@ -2124,7 +2124,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cexpr", "clang-sys", "itertools 0.12.1", @@ -2147,7 +2147,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cexpr", "clang-sys", "itertools 0.13.0", @@ -2231,9 +2231,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] @@ -2244,7 +2244,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b64e60c28b6d25ad92e8b367801ff9aa12b41d05fc8798055d296bace4a60cc" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "serde", ] @@ -2266,18 +2266,6 @@ dependencies = [ "no_std_io2", ] -[[package]] -name = "bitvec" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block" version = "0.1.6" @@ -2302,11 +2290,11 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -2342,25 +2330,25 @@ dependencies = [ [[package]] name = "bon" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3fac94a66da67200398458a25412bcc3f9b6443b5119a6cad9cf3ccfcd8cc6" +checksum = "60eafe0d77c3a2fc292c1d1346c3041b33c0a108085a2afabf672b70f69dbbc9" dependencies = [ "bon-macros", ] [[package]] name = "bon-macros" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4654961ad0494e4774c5c60b4cb4cd0ae9b9d92d039d901638b1dba97ebebf5" +checksum = "bd0f9631d8aaaee112c41985d675ef269e02acbd4f33122836af4f0c5f699ff6" dependencies = [ "darling 0.24.1", "ident_case", "prettyplease 0.3.0", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -2371,9 +2359,9 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "borsh" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ "borsh-derive", "bytes", @@ -2382,15 +2370,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" dependencies = [ "once_cell", "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -2460,9 +2448,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "byte-unit" -version = "5.2.5" +version = "5.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a813de7f2bbedb7dce265b64f1cf5908ebe4d56281ece8d847e98113788b9b0" +checksum = "c719d56f7e96194cfc53460976d3ba51c85719747c9c62ed99981847b551152b" dependencies = [ "rust_decimal", "schemars 1.2.2", @@ -2470,40 +2458,18 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive 0.6.12", - "ptr_meta 0.1.4", - "simdutf8", -] - [[package]] name = "bytecheck" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26333eeac754f0ad8a6bcd0eb0ac012156302e4e16b852b72ee399aea4f12c29" dependencies = [ - "bytecheck_derive 0.8.3", - "ptr_meta 0.3.2", + "bytecheck_derive", + "ptr_meta", "rancor", "simdutf8", ] -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bytecheck_derive" version = "0.8.3" @@ -2512,7 +2478,7 @@ checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -2532,13 +2498,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -2597,7 +2563,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "log", "polling", "rustix 0.38.44", @@ -2747,11 +2713,11 @@ dependencies = [ [[package]] name = "cbc" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -2775,9 +2741,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.4" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" dependencies = [ "find-msvc-tools", "jobserver", @@ -2819,9 +2785,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -2836,18 +2802,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] @@ -2859,7 +2825,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -2926,10 +2892,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clang-sys" version = "1.9.1" @@ -2943,9 +2919,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -2953,9 +2929,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -2965,30 +2941,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.9" +version = "4.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" +checksum = "037e2a1a92236d0aff7e845093f64661d6df4c02c9fcc61a60e9e1d736fa392f" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "clipboard-win" @@ -3095,7 +3071,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" dependencies = [ - "cipher", + "cipher 0.4.4", "dbl", "digest 0.10.7", ] @@ -3137,7 +3113,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block", "cocoa-foundation 0.2.1", "core-foundation 0.10.1", @@ -3167,7 +3143,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3313,9 +3289,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.38" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9" dependencies = [ "brotli", "compression-core", @@ -3325,9 +3301,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" [[package]] name = "computer_use" @@ -3506,7 +3482,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-graphics-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.5.0", @@ -3518,7 +3494,7 @@ name = "core-graphics" version = "0.25.0" source = "git+https://github.com/servo/core-foundation-rs?rev=6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff#6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-graphics-types 0.2.0 (git+https://github.com/servo/core-foundation-rs?rev=6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff)", "foreign-types 0.5.0", @@ -3542,7 +3518,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "libc", ] @@ -3565,6 +3541,12 @@ dependencies = [ "foreign-types 0.5.0", ] +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "core_maths" version = "0.1.1" @@ -3580,7 +3562,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "objc2-audio-toolbox", "objc2-core-audio", @@ -3593,7 +3575,7 @@ name = "cosmic-text" version = "0.12.0" source = "git+https://github.com/warpdotdev/cosmic-text.git?rev=15198beba692162201c0ea8b15222cf5643ea068#15198beba692162201c0ea8b15222cf5643ea068" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "fontdb", "log", "rangemap", @@ -3658,6 +3640,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -3669,9 +3657,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -3693,9 +3681,9 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] @@ -3744,18 +3732,18 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -3763,18 +3751,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crossterm" @@ -3782,13 +3770,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "crossterm_winapi", "derive_more 2.1.1", "document-features", "mio", "parking_lot", - "rustix 1.1.4", + "rustix 1.1.5", "signal-hook", "signal-hook-mio", "winapi", @@ -3898,7 +3886,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -4007,7 +3995,7 @@ dependencies = [ "ouroboros", "proc-macro2", "quote", - "rkyv 0.8.18", + "rkyv", "strsim 0.10.0", "syn 2.0.119", "thiserror 1.0.69", @@ -4019,7 +4007,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7793e157a59a0bd2142a5723860aeaef3aa459feae1467b597351e5f58251904" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "lalrpop-util", "logos", ] @@ -4136,7 +4124,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -4180,7 +4168,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ "darling_core 0.24.1", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -4304,9 +4292,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" dependencies = [ "pem-rfc7468 1.0.0", "zeroize", @@ -4345,13 +4333,13 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -4450,9 +4438,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.12" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715377c6e464cb44bb89bd8487584240516c8d5052bc645d6babc50bb8be46c3" +checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" dependencies = [ "chrono", "diesel_derives", @@ -4464,9 +4452,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.9" +version = "2.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" +checksum = "ecbd51fb6c020672543641167efa4e6417ff7ad76849ed556ace3595e72de03a" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -4595,7 +4583,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.2", + "redox_users 0.5.3", "windows-sys 0.61.2", ] @@ -4622,7 +4610,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -4636,7 +4624,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -4776,7 +4764,7 @@ dependencies = [ "ff", "generic-array", "group", - "hkdf", + "hkdf 0.12.4", "pem-rfc7468 0.7.0", "pkcs8", "rand_core 0.6.4", @@ -4811,7 +4799,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 1.1.4+spec-1.1.0", + "toml 1.1.6+spec-1.1.0", "vswhom", "winreg", ] @@ -4836,11 +4824,17 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" dependencies = [ "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -5035,7 +5029,7 @@ dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.8.9", "num-complex", "pulp 0.22.3", "rayon-core", @@ -5179,9 +5173,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "findshlibs" @@ -5223,12 +5217,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", + "zlib-rs", ] [[package]] @@ -5311,7 +5306,7 @@ name = "font-kit" version = "0.12.0" source = "git+https://github.com/warpdotdev/font-kit.git?rev=a04b225ecb639bb850f21d05212cfdc7465000f3#a04b225ecb639bb850f21d05212cfdc7465000f3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "core-foundation 0.10.1", "core-graphics 0.25.0", @@ -5398,7 +5393,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -5481,7 +5476,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" dependencies = [ - "rustix 1.1.4", + "rustix 1.1.5", "windows-sys 0.59.0", ] @@ -5500,12 +5495,6 @@ dependencies = [ "libc", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futf" version = "0.1.5" @@ -5613,7 +5602,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -5715,7 +5704,7 @@ dependencies = [ "base64 0.22.1", "bimap", "bincode", - "bitflags 2.13.1", + "bitflags 2.13.2", "bitflags-serde-legacy", "block", "blocking", @@ -5811,7 +5800,7 @@ dependencies = [ "http_server", "hyper", "image", - "indexmap 2.14.0", + "indexmap 2.14.2", "infer", "input_classifier", "inquire", @@ -5885,7 +5874,7 @@ dependencies = [ "regex-automata", "remote_server", "repo_metadata", - "reqwest 0.13.4", + "reqwest 0.13.5", "rmcp", "rquickjs", "rust-embed 8.12.0", @@ -5932,7 +5921,7 @@ dependencies = [ "tokio", "tokio-util", "toml 0.8.23", - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.15+spec-1.1.0", "tracing", "tracing-futures", "tracing-subscriber", @@ -6131,7 +6120,7 @@ dependencies = [ "pin-project", "rand 0.8.8", "regex", - "reqwest 0.13.4", + "reqwest 0.13.5", "schemars 1.2.2", "serde", "serde_json", @@ -6250,7 +6239,7 @@ dependencies = [ "instant", "log", "persistence", - "reqwest 0.13.4", + "reqwest 0.13.5", "serde", "thiserror 2.0.20", "websocket", @@ -6402,7 +6391,7 @@ dependencies = [ "parking_lot", "pathfinder_geometry", "rand 0.8.8", - "reqwest 0.13.4", + "reqwest 0.13.5", "schemars 1.2.2", "serde", "serde_json", @@ -6420,7 +6409,7 @@ name = "galaxy_terminal" version = "0.0.0" dependencies = [ "anyhow", - "bitflags 2.13.1", + "bitflags 2.13.2", "bitflags-serde-legacy", "cfg-if", "channel_versions", @@ -6690,7 +6679,7 @@ dependencies = [ "sha2 0.10.9", "tempfile", "thiserror 2.0.20", - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.15+spec-1.1.0", "windows 0.62.2", "windows-registry 0.2.0", "windows-result 0.2.0", @@ -6971,7 +6960,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix 1.1.5", "windows-link 0.2.1", ] @@ -7079,7 +7068,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "libgit2-sys", "log", @@ -7275,9 +7264,9 @@ dependencies = [ [[package]] name = "google-cloud-aiplatform-v1" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4b46c4e50f6a09b85dae39be560c263a20ec06c2b0d5a96d65423936bf238f" +checksum = "289946d961832a02966886f8a415ad734394def699ff82875ff00f03ca13ef28" dependencies = [ "async-trait", "bytes", @@ -7293,7 +7282,7 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "tracing", ] @@ -7307,26 +7296,26 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", ] [[package]] name = "google-cloud-auth" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54aab44c16b8463ae11b165a87c3d484780231f157bb1ed65843d591beb5abd" +checksum = "ff461519b1a948200f163574be072753bcfb462a323f0eb426629d89872dd685" dependencies = [ "async-trait", "aws-lc-rs", - "base64 0.22.1", + "base64 0.23.1", "bytes", - "chrono", "google-cloud-gax", "hex", "hmac 0.13.0", "http 1.5.0", + "jiff", "jsonwebtoken", - "reqwest 0.13.4", + "reqwest 0.13.5", "rustc_version", "rustls", "rustls-pki-types", @@ -7341,9 +7330,9 @@ dependencies = [ [[package]] name = "google-cloud-gax" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a46dd0fd026bbc4a5d84e6ab0c941cee6e3b057976a0bb107fdb5238ce598f" +checksum = "c5615cff28ee59cfe52fbb4c11b8b1e77f650296e2ea4f4c2b7757ac6b19e752" dependencies = [ "bytes", "futures", @@ -7356,13 +7345,14 @@ dependencies = [ "serde_json", "thiserror 2.0.20", "tokio", + "tokio-stream", ] [[package]] name = "google-cloud-gax-internal" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb04c54317ace06d489213f761797240b3046142a9b7ce6b9a82a9d134e193d1" +checksum = "d2766757d877a7a8ac23da9884cb0e3f10ed9b75a0ce59801ce6b19bf9d5819e" dependencies = [ "bytes", "futures", @@ -7383,7 +7373,7 @@ dependencies = [ "pin-project", "prost", "prost-types", - "reqwest 0.13.4", + "reqwest 0.13.5", "rustc_version", "serde", "serde_json", @@ -7399,9 +7389,9 @@ dependencies = [ [[package]] name = "google-cloud-iam-v1" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cdf5acc7ef946ee2db7a7f62bd436d8395a6543b4beef110cdc061fcf578bb" +checksum = "5f962b40234b1531e6ef73f7558871c96e117231e962086c98804323fb8d2c82" dependencies = [ "async-trait", "bytes", @@ -7411,7 +7401,7 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "tracing", ] @@ -7428,15 +7418,15 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "tracing", ] [[package]] name = "google-cloud-longrunning" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6ce05df0aea2c08472983ce2bbbed9483cbb637b89ff69a7c4ef94371fe4f2" +checksum = "1c0363c5389ffda2b55cd8a86eef4b19a3481a48467c91dc5d77f626a9572766" dependencies = [ "async-trait", "bytes", @@ -7446,15 +7436,15 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "tracing", ] [[package]] name = "google-cloud-lro" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cca2b991d619525d72a170ca7f413cb520872702442da22ac9af650a8e786" +checksum = "47af3deef75c14a2983c430898d960c765bddbcc9f9188ca0563108e9227cfe7" dependencies = [ "google-cloud-gax", "google-cloud-gax-internal", @@ -7476,7 +7466,7 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", ] [[package]] @@ -7489,7 +7479,7 @@ dependencies = [ "google-cloud-wkt", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", ] [[package]] @@ -7502,7 +7492,7 @@ dependencies = [ "bytes", "serde", "serde_json", - "serde_with 3.22.0", + "serde_with 3.23.0", "thiserror 2.0.20", "time", "url", @@ -7528,7 +7518,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -7539,7 +7529,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -7673,7 +7663,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -7713,9 +7703,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] [[package]] name = "hashbrown" @@ -7723,7 +7710,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.12", + "ahash", "allocator-api2", ] @@ -7784,9 +7771,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hex" @@ -7839,6 +7826,15 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", +] + [[package]] name = "hmac" version = "0.12.1" @@ -7889,7 +7885,7 @@ dependencies = [ "chacha20poly1305", "digest 0.10.7", "generic-array", - "hkdf", + "hkdf 0.12.4", "hmac 0.12.1", "rand_core 0.9.5", "sha2 0.10.9", @@ -8000,7 +7996,7 @@ dependencies = [ "oauth2", "prevent_sleep", "prost", - "reqwest 0.13.4", + "reqwest 0.13.5", "serde", "serde_json", "serde_urlencoded", @@ -8040,18 +8036,18 @@ checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -8339,9 +8335,9 @@ dependencies = [ [[package]] name = "imgref" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" [[package]] name = "indexmap" @@ -8356,9 +8352,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -8403,7 +8399,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "inotify-sys", "libc", ] @@ -8423,10 +8419,19 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", +] + [[package]] name = "input_classifier" version = "0.0.0" @@ -8463,7 +8468,7 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "crossterm", "dyn-clone", "fuzzy-matcher", @@ -8481,7 +8486,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -8617,9 +8622,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "ipynb_parser" @@ -8754,9 +8759,9 @@ dependencies = [ [[package]] name = "jaq-core" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7561783b20275a6c9cb576e39208b0c635f34ef14357f1f05a2927a774f3adec" +checksum = "f72ba7158fe9fc1bc4baace1b71e6d0d3d27c6839dd87dc1f233dd408f5dd85b" dependencies = [ "dyn-clone", "once_cell", @@ -8787,15 +8792,15 @@ dependencies = [ [[package]] name = "jaq-json" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d801b0b57f10064c4e9f5a4f6c97d0ccf62649b179ff8ac23cd494a3120ee9" +checksum = "4be2f53207ffb1313a2d2ad819cfa8bfaa7936cb85a110eda10fd5da822cec94" dependencies = [ "bstr", "bytes", "foldhash 0.1.5", "hifijson", - "indexmap 2.14.0", + "indexmap 2.14.2", "jaq-core", "jaq-std", "num-bigint", @@ -8807,9 +8812,9 @@ dependencies = [ [[package]] name = "jaq-std" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7941c8de9c591052050550f228c62ef80d3ecbd84c330f5c454bc8ebb7a04089" +checksum = "9f90e485abfd3189510c996e77be55056a51038b0fa5805e6c97b5dcf0d9ab55" dependencies = [ "aho-corasick", "base64 0.22.1", @@ -8841,9 +8846,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ "defmt", "jiff-core", @@ -8858,18 +8863,19 @@ dependencies = [ [[package]] name = "jiff-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ "defmt", + "log", ] [[package]] name = "jiff-static" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" dependencies = [ "jiff-core", "proc-macro2", @@ -8978,9 +8984,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -9007,7 +9013,7 @@ version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798" dependencies = [ - "ahash 0.8.12", + "ahash", "bytecount", "data-encoding", "email_address 0.2.9", @@ -9030,9 +9036,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.4.0" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" dependencies = [ "aws-lc-rs", "base64 0.22.1", @@ -9061,7 +9067,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "serde", "unicode-segmentation", ] @@ -9110,7 +9116,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", ] @@ -9244,14 +9250,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "plain", - "redox_syscall 0.9.3", + "redox_syscall 0.9.4", ] [[package]] @@ -9283,7 +9289,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -9337,7 +9343,7 @@ dependencies = [ "command", "libc", "rand 0.8.8", - "reqwest 0.13.4", + "reqwest 0.13.5", "serde", "serde_json", "shell-words", @@ -9461,18 +9467,18 @@ dependencies = [ [[package]] name = "lru" -version = "0.18.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" +checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25" dependencies = [ "hashbrown 0.17.1", ] [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "lsp" @@ -9744,7 +9750,7 @@ dependencies = [ "log", "oauth2", "pin-project-lite", - "reqwest 0.13.4", + "reqwest 0.13.5", "rmcp", "serde", "serde_json", @@ -9801,9 +9807,9 @@ checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] name = "memo-map" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" +checksum = "5449c8c750f1a07ea702bbd212bd999fceece9b3d1508b17023b3e174583124b" [[package]] name = "memoffset" @@ -9843,7 +9849,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -9925,6 +9931,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mintex" version = "0.1.4" @@ -9933,9 +9949,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -10032,6 +10048,33 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.6", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "munge" version = "0.4.7" @@ -10066,14 +10109,14 @@ checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" dependencies = [ "arrayvec", "bit-set 0.9.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "codespan-reporting", "half", "hashbrown 0.16.1", "hexf-parse", - "indexmap 2.14.0", + "indexmap 2.14.2", "libm", "log", "num-traits", @@ -10178,7 +10221,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "jni-sys 0.3.1", "log", "ndk-sys", @@ -10226,7 +10269,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -10239,7 +10282,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -10306,7 +10349,7 @@ name = "notify" version = "8.0.0" source = "git+https://github.com/warpdotdev/notify?rev=91b719849bc04ef251bcb2cc61076b099204e970#91b719849bc04ef251bcb2cc61076b099204e970" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "filetime", "fsevent-sys", "inotify", @@ -10581,7 +10624,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -10597,7 +10640,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -10611,7 +10654,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "objc2 0.6.4", "objc2-core-audio", @@ -10626,7 +10669,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -10647,7 +10690,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location 0.2.2", @@ -10660,7 +10703,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -10695,7 +10738,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", ] @@ -10705,7 +10748,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -10727,7 +10770,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "dispatch2", "libc", @@ -10740,7 +10783,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "dispatch2", "libc", @@ -10800,7 +10843,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -10818,7 +10861,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "dispatch", "libc", @@ -10831,7 +10874,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -10854,7 +10897,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-core-foundation", ] @@ -10877,7 +10920,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -10889,7 +10932,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "dispatch2", "objc2 0.6.4", @@ -10903,7 +10946,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -10916,7 +10959,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -10939,7 +10982,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit 0.2.2", @@ -10960,7 +11003,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "objc2 0.6.4", "objc2-cloud-kit 0.3.2", @@ -10992,7 +11035,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location 0.2.2", @@ -11075,7 +11118,7 @@ version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "once_cell", "onig_sys", @@ -11105,9 +11148,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open" -version = "5.4.2" +version = "5.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade3be4664bc1ef537ce133015f04c176b737815c2ba9fd60edf212d6e90dd55" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" dependencies = [ "is-wsl", "libc", @@ -11140,7 +11183,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "foreign-types 0.3.2", "libc", @@ -11284,7 +11327,7 @@ dependencies = [ "ort-sys", "smallvec", "tracing", - "ureq 3.4.0", + "ureq 3.4.2", ] [[package]] @@ -11295,7 +11338,7 @@ checksum = "cf211e3776eea6aec988552fa118dd746d70e1b1e5e244058d1c98015f3e5872" dependencies = [ "hmac-sha256", "lzma-rust2", - "ureq 3.4.0", + "ureq 3.4.2", ] [[package]] @@ -11576,9 +11619,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" dependencies = [ "memchr", "ucd-trie", @@ -11586,9 +11629,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" +checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" dependencies = [ "pest", "pest_generator", @@ -11596,9 +11639,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" +checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" dependencies = [ "pest", "pest_meta", @@ -11609,9 +11652,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" +checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" dependencies = [ "pest", ] @@ -11624,7 +11667,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset 0.5.7", "hashbrown 0.15.5", - "indexmap 2.14.0", + "indexmap 2.14.2", "serde", ] @@ -11774,13 +11817,13 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml", + "base64 0.23.1", + "indexmap 2.14.2", + "quick-xml 0.42.0", "serde", "time", ] @@ -11819,11 +11862,11 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", ] [[package]] @@ -11836,7 +11879,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix 1.1.5", "windows-sys 0.61.2", ] @@ -11880,9 +11923,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -12012,7 +12055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" dependencies = [ "proc-macro2", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -12056,7 +12099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", - "indexmap 2.14.0", + "indexmap 2.14.2", "serde", ] @@ -12076,7 +12119,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.15+spec-1.1.0", ] [[package]] @@ -12143,7 +12186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.14.0", + "indexmap 2.14.2", "nix 0.31.3", "tokio", "tracing", @@ -12285,7 +12328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" dependencies = [ "anyhow", - "indexmap 2.14.0", + "indexmap 2.14.2", "log", "protobuf", "protobuf-support", @@ -12305,9 +12348,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.226" +version = "2.1.232" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc88482eea924ca3a2f56a547454169af58deef35567965eb4fc2392a834841" +checksum = "62834e308cc83aea5e30cd8c80b8aa82cdb104a3240c7f210d4f68d46e29f308" dependencies = [ "psl-types", ] @@ -12318,33 +12361,13 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive 0.1.4", -] - [[package]] name = "ptr_meta" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" dependencies = [ - "ptr_meta_derive 0.3.2", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "ptr_meta_derive", ] [[package]] @@ -12355,7 +12378,7 @@ checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -12423,10 +12446,19 @@ dependencies = [ ] [[package]] -name = "quinn" -version = "0.11.11" +name = "quick-xml" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ "bytes", "cfg_aliases", @@ -12444,9 +12476,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" dependencies = [ "aws-lc-rs", "bytes", @@ -12523,19 +12555,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rancor" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" dependencies = [ - "ptr_meta 0.3.2", + "ptr_meta", ] [[package]] @@ -12566,7 +12592,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20 0.10.1", + "chacha20 0.10.2", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -12669,13 +12695,13 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "compact_str", "critical-section", "hashbrown 0.17.1", "itertools 0.14.0", "kasuari", - "lru 0.18.2", + "lru 0.18.4", "palette 0.7.7", "serde", "strum 0.28.0", @@ -12734,7 +12760,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "hashbrown 0.17.1", "indoc", "instability", @@ -12804,7 +12830,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -12908,16 +12934,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] name = "redox_syscall" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -12933,11 +12959,10 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", "thiserror 2.0.20", ] @@ -12959,7 +12984,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -12968,7 +12993,7 @@ version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010" dependencies = [ - "ahash 0.8.12", + "ahash", "fluent-uri 0.4.1", "getrandom 0.3.4", "hashbrown 0.16.1", @@ -13046,22 +13071,13 @@ dependencies = [ "uuid", ] -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck 0.6.12", -] - [[package]] name = "rend" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" dependencies = [ - "bytecheck 0.8.3", + "bytecheck", ] [[package]] @@ -13147,11 +13163,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "encoding_rs", "futures-channel", @@ -13264,12 +13280,12 @@ dependencies = [ "futures-timer", "glob", "http 1.5.0", - "indexmap 2.14.0", + "indexmap 2.14.2", "mime", "mime_guess", "ordered-float 5.5.0", "pin-project-lite", - "reqwest 0.13.4", + "reqwest 0.13.5", "rig-derive", "schemars 1.2.2", "serde", @@ -13324,54 +13340,25 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rkyv" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" -dependencies = [ - "bitvec", - "bytecheck 0.6.12", - "bytes", - "hashbrown 0.12.3", - "ptr_meta 0.1.4", - "rend 0.4.2", - "rkyv_derive 0.7.46", - "seahash", - "tinyvec", - "uuid", -] - [[package]] name = "rkyv" version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" dependencies = [ - "bytecheck 0.8.3", + "bytecheck", "bytes", "hashbrown 0.17.1", - "indexmap 2.14.0", + "indexmap 2.14.2", "munge", - "ptr_meta 0.3.2", + "ptr_meta", "rancor", - "rend 0.5.4", - "rkyv_derive 0.8.18", + "rend", + "rkyv_derive", "tinyvec", "uuid", ] -[[package]] -name = "rkyv_derive" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rkyv_derive" version = "0.8.18" @@ -13380,7 +13367,7 @@ checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -13398,7 +13385,7 @@ dependencies = [ "pastey 0.2.3", "pin-project-lite", "process-wrap", - "reqwest 0.13.4", + "reqwest 0.13.5", "rmcp-macros", "schemars 1.2.2", "serde", @@ -13592,16 +13579,16 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.42.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.8", - "rkyv 0.7.46", + "rand 0.9.5", "serde", "serde_json", "wasm-bindgen", @@ -13654,7 +13641,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.15", @@ -13663,11 +13650,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.12.1", @@ -13676,9 +13663,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "aws-lc-rs", "log", @@ -13784,7 +13771,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytemuck", "core_maths", "log", @@ -13896,7 +13883,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -13924,12 +13911,6 @@ dependencies = [ "tiny-skia 0.11.4", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "sec1" version = "0.7.3" @@ -13946,20 +13927,20 @@ dependencies = [ [[package]] name = "secret-service" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14" +checksum = "5107b24b91445dd2aa449a258a1807b63240942157292354dc5bfdbeb8bc6db8" dependencies = [ - "aes", + "aes 0.9.3", "cbc", "futures-util", - "generic-array", - "getrandom 0.2.17", - "hkdf", + "getrandom 0.4.3", + "hkdf 0.13.0", + "hybrid-array", "num", "once_cell", "serde", - "sha2 0.10.9", + "sha2 0.11.0", "zbus", ] @@ -13969,7 +13950,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -13982,7 +13963,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -14088,7 +14069,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -14099,7 +14080,7 @@ checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -14108,7 +14089,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "itoa", "memchr", "serde", @@ -14154,7 +14135,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -14205,22 +14186,22 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.0", + "indexmap 2.14.2", "jiff", "schemars 0.9.0", "schemars 1.2.2", "serde_core", "serde_json", - "serde_with_macros 3.22.0", + "serde_with_macros 3.23.0", "time", ] @@ -14238,14 +14219,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ - "darling 0.23.0", + "darling 0.24.1", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] @@ -14266,7 +14247,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "itoa", "ryu", "serde", @@ -14404,7 +14385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "digest 0.11.3", ] @@ -14606,9 +14587,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" [[package]] name = "smithay-client-toolkit" @@ -14616,7 +14597,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "calloop", "calloop-wayland-source", "cursor-icon", @@ -14689,7 +14670,7 @@ version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -14728,9 +14709,9 @@ dependencies = [ [[package]] name = "sse-stream" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c123f296ade4ec4b8b0f6162116e6629f5146922ca5ab40ca9d3c2e73ab4761e" +checksum = "c25ac7aff0abd1dbc474536e40416e1102c7dd9bfba0b9861c6d357f835dcfb4" dependencies = [ "bytes", "futures-util", @@ -14941,9 +14922,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -14970,6 +14951,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "syntax_tree" version = "0.1.0" @@ -15026,7 +15018,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "enum-as-inner", "libc", @@ -15055,7 +15047,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -15087,9 +15079,9 @@ checksum = "07dd1d452d2c3dc94a4e1c5c3c9a3cc88c2ef5926674b75881e454c4dc3a14c4" [[package]] name = "tantivy" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edde6a10743fff00a4e1a8c9ef020bf5f3cbad301b7d2d39f2b07f123c4eac07" +checksum = "861facfabd71044968f364837f9a083b56464ba5a59079f88706ee5c451ca069" dependencies = [ "aho-corasick", "arc-swap", @@ -15233,12 +15225,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tar" version = "0.4.46" @@ -15276,7 +15262,7 @@ dependencies = [ "fastrand 2.5.0", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix 1.1.5", "windows-sys 0.61.2", ] @@ -15306,9 +15292,9 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "parking_lot", - "rustix 1.1.4", + "rustix 1.1.5", "signal-hook", "windows-sys 0.61.2", ] @@ -15348,7 +15334,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64 0.22.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "fancy-regex 0.11.0", "filedescriptor", "finl_unicode", @@ -15419,7 +15405,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -15525,7 +15511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d881bb6d44ed6b15f58c85f6fb6d3e1262200c0d540c541e94fe16e726ff4e5" dependencies = [ "aead", - "aes", + "aes 0.8.4", "aes-gcm", "aes-gcm-siv", "chacha20poly1305", @@ -15542,7 +15528,7 @@ version = "0.3.0" source = "git+https://github.com/warpdotdev/tink-rust?rev=54b9ac9af93b0c08b446a7bc0582836c9403a71b#54b9ac9af93b0c08b446a7bc0582836c9403a71b" dependencies = [ "digest 0.10.7", - "hkdf", + "hkdf 0.12.4", "lazy_static", "rand 0.8.8", "serde", @@ -15594,10 +15580,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78d1cabf040b08759a32d8b2a830c707b7c578fc92f68a3eeca4231cd6dfca33" dependencies = [ - "aes", + "aes 0.8.4", "cmac", "digest 0.10.7", - "hkdf", + "hkdf 0.12.4", "hmac 0.12.1", "sha-1", "sha2 0.10.9", @@ -15689,18 +15675,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "titlecase" @@ -15724,7 +15701,7 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476" dependencies = [ - "ahash 0.8.12", + "ahash", "aho-corasick", "compact_str", "dary_heap", @@ -15777,7 +15754,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -15792,9 +15769,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ "rustls", "tokio", @@ -15809,6 +15786,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -15878,11 +15856,11 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "serde_core", "serde_spanned 1.1.1", "toml_datetime 1.1.1+spec-1.1.0", @@ -15933,7 +15911,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -15944,7 +15922,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -15954,11 +15932,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.15+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ - "indexmap 2.14.0", + "indexmap 2.14.2", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", @@ -16032,7 +16010,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.14.0", + "indexmap 2.14.2", "pin-project-lite", "slab", "sync_wrapper", @@ -16050,7 +16028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-core", "futures-util", @@ -16195,9 +16173,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" +checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081" [[package]] name = "tree_magic_mini" @@ -16266,9 +16244,9 @@ dependencies = [ [[package]] name = "twox-hash" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" +checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a" [[package]] name = "typed-arena" @@ -16321,7 +16299,7 @@ checksum = "f153acc4e99a5f2a5aefa09fb078be54e26271b2813f6041200b224c098d8328" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -16445,9 +16423,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] name = "unicode-linebreak" @@ -16585,12 +16563,12 @@ dependencies = [ [[package]] name = "ureq" -version = "3.4.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" dependencies = [ "base64 0.23.1", - "der 0.8.1", + "der 0.8.2", "log", "native-tls", "percent-encoding", @@ -16603,9 +16581,9 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" dependencies = [ "base64 0.23.1", "http 1.5.0", @@ -16709,9 +16687,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.25.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "atomic", "getrandom 0.4.3", @@ -17098,9 +17076,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -17111,9 +17089,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -17121,9 +17099,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -17131,22 +17109,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -17197,7 +17175,7 @@ checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" dependencies = [ "cc", "downcast-rs 1.2.1", - "rustix 1.1.4", + "rustix 1.1.5", "scoped-tls", "smallvec", "wayland-sys", @@ -17209,8 +17187,8 @@ version = "0.31.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ - "bitflags 2.13.1", - "rustix 1.1.4", + "bitflags 2.13.2", + "rustix 1.1.5", "wayland-backend", "wayland-scanner", ] @@ -17221,7 +17199,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cursor-icon", "wayland-backend", ] @@ -17232,7 +17210,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "rustix 1.1.4", + "rustix 1.1.5", "wayland-client", "xcursor", ] @@ -17243,7 +17221,7 @@ version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "wayland-backend", "wayland-client", "wayland-scanner", @@ -17255,7 +17233,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "wayland-backend", "wayland-client", "wayland-protocols", @@ -17268,7 +17246,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "wayland-backend", "wayland-client", "wayland-protocols", @@ -17282,7 +17260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.41.0", "quote", ] @@ -17300,9 +17278,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -17483,7 +17461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" dependencies = [ "arrayvec", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytemuck", "cfg-if", "cfg_aliases", @@ -17514,12 +17492,12 @@ dependencies = [ "arrayvec", "bit-set 0.9.1", "bit-vec 0.9.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytemuck", "cfg_aliases", "document-features", "hashbrown 0.16.1", - "indexmap 2.14.0", + "indexmap 2.14.2", "log", "naga", "once_cell", @@ -17585,7 +17563,7 @@ dependencies = [ "arrayvec", "ash", "bit-set 0.9.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.6.2", "bytemuck", "cfg-if", @@ -17645,7 +17623,7 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytemuck", "js-sys", "log", @@ -18212,10 +18190,10 @@ name = "winit" version = "0.30.13" source = "git+https://github.com/warpdotdev/winit.git?rev=a4e0ecb5f9626ccac9445a73dc28354b52423abc#a4e0ecb5f9626ccac9445a73dc28354b52423abc" dependencies = [ - "ahash 0.8.12", + "ahash", "android-activity", "atomic-waker", - "bitflags 2.13.1", + "bitflags 2.13.2", "block2 0.5.1", "bytemuck", "calloop", @@ -18320,7 +18298,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.4", + "rustix 1.1.5", "thiserror 2.0.20", "tree_magic_mini", "wayland-backend", @@ -18364,15 +18342,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -18395,7 +18364,7 @@ dependencies = [ "libc", "libloading 0.8.9", "once_cell", - "rustix 1.1.4", + "rustix 1.1.5", "x11rb-protocol", ] @@ -18422,7 +18391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.4", + "rustix 1.1.5", ] [[package]] @@ -18443,7 +18412,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dlib", "log", "once_cell", @@ -18550,7 +18519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", - "yoke-derive 0.8.2", + "yoke-derive 0.8.3", "zerofrom", ] @@ -18563,19 +18532,19 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "synstructure", + "synstructure 0.13.2", ] [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] @@ -18600,7 +18569,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.4", + "rustix 1.1.5", "serde", "serde_repr", "tracing", @@ -18622,7 +18591,7 @@ dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", "zbus_names", "zvariant", "zvariant_utils", @@ -18656,18 +18625,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -18685,14 +18654,14 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.6", + "synstructure 0.14.0", ] [[package]] @@ -18745,7 +18714,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", ] [[package]] @@ -18754,7 +18723,7 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ - "aes", + "aes 0.8.4", "arbitrary", "bzip2", "constant_time_eq", @@ -18765,7 +18734,7 @@ dependencies = [ "flate2", "getrandom 0.3.4", "hmac 0.12.1", - "indexmap 2.14.0", + "indexmap 2.14.2", "lzma-rs", "memchr", "pbkdf2", @@ -18785,11 +18754,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "crc32fast", - "indexmap 2.14.0", + "indexmap 2.14.2", "memchr", "typed-path 0.12.3", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" @@ -18819,18 +18794,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.4" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", @@ -18885,7 +18860,7 @@ dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.6", "zvariant_utils", ] @@ -18898,6 +18873,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 3.0.4", + "syn 3.0.6", "winnow 1.0.4", ] diff --git a/app/src/ai/active_agent_views_model.rs b/app/src/ai/active_agent_views_model.rs index 773767cc..4a2e9db6 100644 --- a/app/src/ai/active_agent_views_model.rs +++ b/app/src/ai/active_agent_views_model.rs @@ -255,6 +255,17 @@ impl ActiveAgentViewsModel { } } + /// Whether the terminal view still belongs to an attached pane, including hidden child panes. + pub(crate) fn is_terminal_view_attached( + &self, + terminal_view_id: EntityId, + ctx: &AppContext, + ) -> bool { + self.agent_view_handles + .get(&terminal_view_id) + .is_some_and(|handles| handles.controller.upgrade(ctx).is_some()) + } + pub fn handle_pane_focus_change( &mut self, window_id: WindowId, diff --git a/app/src/ai/blocklist/agent_view/mod.rs b/app/src/ai/blocklist/agent_view/mod.rs index e1e0d8df..106e3d41 100644 --- a/app/src/ai/blocklist/agent_view/mod.rs +++ b/app/src/ai/blocklist/agent_view/mod.rs @@ -11,7 +11,7 @@ pub(crate) mod orchestration_conversation_links; pub mod orchestration_pill_bar; pub mod orchestration_pill_bar_model; pub mod shortcuts; -pub(crate) mod subagent_inline_panel; +pub(crate) mod subagent_link; mod zero_state_block; use std::sync::LazyLock; diff --git a/app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs b/app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs index 882734df..d197320a 100644 --- a/app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs +++ b/app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs @@ -65,7 +65,7 @@ use crate::menu::{Event as MenuEvent, Menu, MenuItem, MenuItemFields}; use crate::pane_group::pane::view::PaneHeaderAction; use crate::terminal::view::TerminalAction; use crate::ui_components::icon_with_status::{ - self, render_icon_with_status_with_badge_style, BadgeInnerShape, IconWithStatusVariant, + render_icon_with_status_with_badge_style, BadgeInnerShape, IconWithStatusVariant, StatusBadgeStyle, }; use crate::ui_components::icons::Icon; @@ -75,7 +75,15 @@ const PILL_HEIGHT: f32 = 22.; const PILL_RADIUS: f32 = PILL_HEIGHT / 2.; const AVATAR_SIZE: f32 = 16.; const PILL_AVATAR_SLOT_SIZE: f32 = 20.; -const PILL_AVATAR_DISC_SIZE: f32 = PILL_AVATAR_SLOT_SIZE * icon_with_status::CIRCLE_RATIO; + +/// Visible avatar disc diameter, per design. +const PILL_AVATAR_DISC_SIZE: f32 = 15.; +/// Gap between the avatar disc and each of the pill's horizontal edges. The +/// disc is dead-centre in the pill, so this is symmetric: (22 - 15) / 2 = 3.5. +const PILL_AVATAR_VERTICAL_PADDING: f32 = (PILL_HEIGHT - PILL_AVATAR_DISC_SIZE) / 2.; +/// Square box the status badge is sized and anchored against. It does *not* +/// size the avatar disc (that is [`PILL_AVATAR_DISC_SIZE`]) — it only reserves +/// the square whose bottom-right corner the badge hangs off. const AVATAR_WITH_STATUS_TOTAL_SIZE: f32 = PILL_AVATAR_SLOT_SIZE; const PILL_LABEL_MAX_WIDTH: f32 = 83.; const PILL_ROW_GAP: f32 = 8.; @@ -1590,8 +1598,17 @@ fn render_status_badge( .finish() } +/// Places a pill's leading avatar content — an +/// [`AVATAR_WITH_STATUS_TOTAL_SIZE`] box built by [`render_avatar_lockup_box`], +/// with or without a status badge layered on it — in the fixed-width leading +/// slot. The slot spans the full pill height so hover swaps (avatar ↔ pin +/// button) never shift the label. +/// +/// The box is bottom-aligned rather than centered: the status badge hangs off +/// its bottom-right corner and design wants that badge flush with the pill's +/// bottom edge, so the box's bottom has to be the pill's bottom. fn render_avatar_slot(avatar: Box) -> Box { - ConstrainedBox::new(Align::new(avatar).finish()) + ConstrainedBox::new(Align::new(avatar).bottom_left().finish()) .with_width(PILL_AVATAR_SLOT_SIZE) .with_height(PILL_HEIGHT) .finish() @@ -1669,38 +1686,64 @@ fn render_pinned_divider(app: &AppContext) -> Box { .finish() } -/// Pin glyph centered in an avatar-sized hit target so swapping in and out -/// on hover doesn't jitter sibling pill widths. Solid glyph when pinned, -/// outline when unpinned. -fn render_pin_glyph_centered(is_pinned: bool, icon_color: ColorU) -> Box { - let icon_variant = if is_pinned { +/// The clickable pin button a child pill shows in place of its avatar while +/// hovered: a circle occupying exactly the avatar disc's rect. +/// +/// Placement deliberately goes through the same +/// [`render_avatar_slot`] / [`render_avatar_lockup_box`] pair the disc itself +/// uses, and the circle is sized off [`PILL_AVATAR_DISC_SIZE`], so the swap +/// cannot shift by a pixel and the two cannot drift apart if the disc's +/// geometry is ever retuned. +/// +/// The glyph is placed by explicit padding rather than by a centering +/// container, which keeps it exact regardless of how the surrounding box +/// behaves, and lets [`PIN_GLYPH_OPTICAL_DROP`] bias it downward without +/// moving the circle. +fn render_pin_button( + is_pinned: bool, + icon_color: ColorU, + mouse_state: MouseStateHandle, + conversation_id: AIConversationId, +) -> Box { + // Tint with the pill's own contrasting colour rather than a fixed + // foreground overlay. `fg_overlay_1` is the foreground at 5% opacity, and a + // selected pill's background *is* the foreground colour, so the old fill + // painted a colour onto itself and the hover state was invisible on every + // selected chip — which, since the bar anchors on the parent of whatever + // leaf you are viewing, is the common case rather than an edge case. + let hover_background = coloru_with_opacity(icon_color, PIN_BUTTON_HOVER_OPACITY); + let glyph_size = PILL_AVATAR_DISC_SIZE * PIN_GLYPH_RATIO; + let icon = if is_pinned { Icon::PinFilled } else { Icon::Pin }; - let glyph: Box = - ConstrainedBox::new(icon_variant.to_warpui_icon(icon_color.into()).finish()) - .with_width(PILL_ICON_SIZE) - .with_height(PILL_ICON_SIZE) + let button = Hoverable::new(mouse_state, move |hover_state| { + let glyph = ConstrainedBox::new(icon.to_warpui_icon(icon_color.into()).finish()) + .with_width(glyph_size) + .with_height(glyph_size) .finish(); - - let centered = Flex::column() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::Center) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child( - Flex::row() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::Center) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(glyph) - .finish(), - ) - .finish(); - ConstrainedBox::new(centered) - .with_width(PILL_AVATAR_SLOT_SIZE) - .with_height(PILL_ICON_BUTTON_SIZE) - .finish() + // Top and bottom padding still sum to twice `padding`, so the circle + // keeps the avatar disc's rect exactly; only the glyph inside it moves. + let padding = (PILL_AVATAR_DISC_SIZE - glyph_size) / 2.; + let mut circle = Container::new(glyph) + .with_horizontal_padding(padding) + .with_padding_top(padding + PIN_GLYPH_OPTICAL_DROP) + .with_padding_bottom(padding - PIN_GLYPH_OPTICAL_DROP) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels( + PILL_AVATAR_DISC_SIZE / 2., + ))); + if hover_state.is_hovered() || hover_state.is_clicked() { + circle = circle.with_background(hover_background); + } + circle.finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, _app, _| { + ctx.dispatch_typed_action(OrchestrationPillBarAction::TogglePin(conversation_id)); + }) + .finish(); + render_avatar_slot(render_avatar_lockup_box(button)) } fn render_pill( @@ -1833,41 +1876,21 @@ fn render_pill( theme, appearance, ), - None => render_avatar_slot(render_avatar_disc( - avatar_color, - avatar_glyph, - PILL_AVATAR_DISC_SIZE, - theme, - appearance, - )), + None => render_pill_avatar(avatar_color, avatar_glyph, theme, appearance), }, PillKind::Child => { if show_pin_glyph { // Hovered: the leading slot becomes the clickable pin - // button. We only attach the Hoverable + TogglePin - // click handler here so that when the avatar is the + // button. The Hoverable + TogglePin click handler is + // attached only here so that when the avatar is the // visible content (not hovered), clicks bubble up to // the outer pill body and navigate as expected. - let pin_button_mouse_state = pin_button_mouse_state.clone(); - Hoverable::new(pin_button_mouse_state, move |pin_hover_state| { - let pin_button_hovered = - pin_hover_state.is_hovered() || pin_hover_state.is_clicked(); - let mut container = - Container::new(render_pin_glyph_centered(is_pinned, text_color)) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.))); - if pin_button_hovered { - container = - container.with_background(internal_colors::fg_overlay_1(theme)); - } - container.finish() - }) - .with_cursor(Cursor::PointingHand) - .on_click(move |ctx, _app, _| { - ctx.dispatch_typed_action(OrchestrationPillBarAction::TogglePin( - conversation_id, - )); - }) - .finish() + render_pin_button( + is_pinned, + text_color, + pin_button_mouse_state.clone(), + conversation_id, + ) } else if let Some(ref status) = status { render_avatar_with_status_overlay( avatar_color, @@ -1879,13 +1902,7 @@ fn render_pill( appearance, ) } else { - render_avatar_slot(render_avatar_disc( - avatar_color, - avatar_glyph, - PILL_AVATAR_DISC_SIZE, - theme, - appearance, - )) + render_pill_avatar(avatar_color, avatar_glyph, theme, appearance) } } }; @@ -2066,13 +2083,123 @@ fn render_overflow_button( SavePosition::new(button, &overflow_button_position_id(conversation_id)).finish() } +/// Pin glyph size as a fraction of the avatar disc it sits in. Calibrated +/// against the letter it replaces: matching the letter's ink height exactly +/// read too *small*, because a thin outline carries less visual weight than a +/// solid letterform, so design asked for roughly 4px more. This is the knob to +/// nudge if it still reads wrong. +const PIN_GLYPH_RATIO: f32 = 0.71; + +/// Downward nudge of the pin glyph inside its circle. +/// +/// This is an *optical* correction, not a geometry one — do not "fix" it to +/// zero because the arithmetic says centered. The pin's mass is concentrated +/// in its head, so a geometrically centered glyph reads as sitting high. +/// +/// Absolute pixels rather than a ratio because the pin is only ever drawn in +/// the chip's [`PILL_AVATAR_DISC_SIZE`] circle. If it gains another size, this +/// needs revisiting rather than silently scaling. +const PIN_GLYPH_OPTICAL_DROP: f32 = 1.; + +/// Opacity of the pin button's hover tint, over the pill's contrasting colour. +/// A little stronger than the 5% `fg_overlay_1` used to apply, because that +/// colour is nearer the pill's own background than the contrasting one is. +const PIN_BUTTON_HOVER_OPACITY: u8 = 8; + +/// Cutout-ring diameter of the status badge, per design. +const PILL_BADGE_RING_SIZE: f32 = 11.; +/// Bounding box of the status icon inside that ring, per design. The 1px it +/// leaves on each side is the visible cutout. +const PILL_BADGE_ICON_SIZE: f32 = 9.; + +/// `icon_with_status` expresses badge geometry as fractions of the box the +/// badge is anchored to, so convert the designed absolute sizes once here +/// rather than restating them as ratios. const PILL_BADGE_STYLE: StatusBadgeStyle = StatusBadgeStyle { - ring_ratio: 0.57, - icon_ratio: 0.36, + ring_ratio: PILL_BADGE_RING_SIZE / AVATAR_WITH_STATUS_TOTAL_SIZE, + icon_ratio: PILL_BADGE_ICON_SIZE / AVATAR_WITH_STATUS_TOTAL_SIZE, inner_shape: BadgeInnerShape::RoundedSquare { radius_px: 2.0 }, }; + +/// Extra overhang of the status badge past the avatar circle's bottom-right +/// edge, as a signed fraction of [`AVATAR_WITH_STATUS_TOTAL_SIZE`] added to +/// `icon_with_status`'s default overhang. +/// +/// It is not a free parameter: `0.05` is exactly what cancels that helper's +/// built-in `0.19` default, putting the badge's bottom-right corner on the +/// lockup box's own bottom-right corner. Since the box is bottom-aligned in +/// the slot, that is what makes the ring flush with the pill's bottom edge. const PILL_BADGE_OVERHANG_RATIO: f32 = 0.05; +/// Top inset of the avatar disc inside the [`AVATAR_WITH_STATUS_TOTAL_SIZE`] +/// box. The box is bottom-aligned in the [`PILL_HEIGHT`]-tall slot so the +/// badge anchored to its bottom-right corner reaches the pill's bottom edge, +/// so this inset plus that bottom-alignment offset has to add up to +/// [`PILL_AVATAR_VERTICAL_PADDING`]: 2 + 1.5 = 3.5. +const PILL_AVATAR_LOCKUP_TOP_INSET: f32 = + PILL_AVATAR_VERTICAL_PADDING - (PILL_HEIGHT - AVATAR_WITH_STATUS_TOTAL_SIZE); + +/// Places the avatar disc inside the square box that the status badge is +/// anchored against, applying the designed padding. Shared by the +/// plain and status-badged paths so a pill's avatar lands in exactly the same +/// spot whether or not it currently has a status. +/// +/// The disc hugs the box's leading edge and sits +/// [`PILL_AVATAR_LOCKUP_TOP_INSET`] down from its top edge. Only the vertical +/// placement is ours to choose; horizontally the disc has to stay flush left, +/// because the badge is anchored to the box's bottom-right corner and every +/// pixel the disc moves right is a pixel more of it the badge's cutout ring +/// eats — enough to swallow the agent's initial. +fn render_avatar_lockup_box(disc: Box) -> Box { + ConstrainedBox::new( + Container::new(Align::new(disc).top_left().finish()) + .with_padding_top(PILL_AVATAR_LOCKUP_TOP_INSET) + .finish(), + ) + .with_width(AVATAR_WITH_STATUS_TOTAL_SIZE) + .with_height(AVATAR_WITH_STATUS_TOTAL_SIZE) + .finish() +} + +/// Renders the leading avatar for a pill with no status badge. +fn render_pill_avatar( + avatar_color: ColorU, + glyph: AvatarGlyph, + theme: &WarpTheme, + appearance: &Appearance, +) -> Box { + render_avatar_slot(render_avatar_lockup_box(render_avatar_disc( + avatar_color, + glyph, + PILL_AVATAR_DISC_SIZE, + theme, + appearance, + ))) +} + +/// Renders the leading avatar for a pill that has a status: the avatar disc +/// plus its status badge, in the same slot [`render_pill_avatar`] uses. +/// +/// Geometry, in pill-content coordinates (the pill is [`PILL_HEIGHT`] = 22 +/// tall with a [`PILL_RADIUS`] = 11 stadium cap, and the leading slot spans +/// x = 4..24 after [`PILL_HORIZONTAL_PADDING_LEFT`]): +/// * Lockup box: [`AVATAR_WITH_STATUS_TOTAL_SIZE`] = 20 square, bottom-aligned +/// in the 22-tall slot, so it spans y = 2..22. +/// * Avatar disc: [`PILL_AVATAR_DISC_SIZE`] = 15, inset +/// [`PILL_AVATAR_LOCKUP_TOP_INSET`] = 1.5 from the box's top and flush with +/// its left edge, so it spans y = 3.5..18.5 and x = 4..19 — dead-centre in +/// the pill, [`PILL_AVATAR_VERTICAL_PADDING`] = 3.5 above and below. +/// * Status badge: [`PILL_BADGE_RING_SIZE`] = 11 cutout ring, anchored BR-to-BR +/// with `corner_overlay_offset(20, 0.05)` = 0, so its BR lands on the lockup +/// box's BR at (24, 22) and the ring spans y = 11..22, x = 13..24. That is +/// 9 horizontally and 7.5 vertically in from the disc's top-left, per design, +/// and its bottom is flush with the pill's — an emergent property of the +/// box being bottom-aligned, not a hardcoded 22. The ring starts at +/// x = 13 > `PILL_RADIUS`, so it sits in the pill's flat-bottom region and +/// is tangent to that edge rather than clipped by the rounded cap (only +/// x < 11 is governed by the cap's arc). +/// * Status icon: [`PILL_BADGE_ICON_SIZE`] = 9 bounding box centred in the +/// ring, leaving the 1px cutout. fn render_avatar_with_status_overlay( avatar_color: ColorU, glyph: AvatarGlyph, @@ -2082,15 +2209,13 @@ fn render_avatar_with_status_overlay( theme: &WarpTheme, appearance: &Appearance, ) -> Box { - // Top-left anchor inside the helper's `total_size` box so the disc sits - // where Figma places it (TL of the slot, leaving the BR for the badge). - let avatar = render_avatar_disc( + let avatar = render_avatar_lockup_box(render_avatar_disc( avatar_color, glyph, - icon_with_status::circle_size(AVATAR_WITH_STATUS_TOTAL_SIZE), + PILL_AVATAR_DISC_SIZE, theme, appearance, - ); + )); let lockup = render_icon_with_status_with_badge_style( IconWithStatusVariant::CustomAvatar { avatar, @@ -2104,19 +2229,9 @@ fn render_avatar_with_status_overlay( // Cutout ring color for the local badge; ignored by the cloud path. pill_background.into(), ); - // Bottom-anchor the lockup in the pill so the badge BR sits flush with - // the pill's bottom edge (matches Figma). - ConstrainedBox::new( - Flex::column() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::End) - .with_cross_axis_alignment(CrossAxisAlignment::Start) - .with_child(lockup) - .finish(), - ) - .with_width(PILL_AVATAR_SLOT_SIZE) - .with_height(PILL_HEIGHT) - .finish() + // Same slot helper as the no-status path, so both share one placement + // rule and the leading slot keeps identical width across the swap. + render_avatar_slot(lockup) } /// Renders the avatar circle as a colored disc with a centered glyph (letter @@ -2148,6 +2263,11 @@ fn render_avatar_disc( weight: Weight::Bold, ..Default::default() }) + // The default 1.2 ratio pads the text box with leading, so + // centering the box leaves the letter's ink sitting high in + // the disc. At 1.0 the box is the glyph, and centering it + // centers what you can see. + .with_line_height_ratio(1.) .finish() } AvatarGlyph::Icon(icon) => { @@ -2158,27 +2278,10 @@ fn render_avatar_disc( } }; - // Center the glyph on top of the disc both horizontally and vertically by - // using `MainAxisAlignment::Center` (along axis) and - // `CrossAxisAlignment::Center` (perpendicular) on both Flex containers. - let glyph_centered = ConstrainedBox::new( - Flex::column() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::Center) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child( - Flex::row() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::Center) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(glyph_element) - .finish(), - ) - .finish(), - ) - .with_width(size) - .with_height(size) - .finish(); + let glyph_centered = ConstrainedBox::new(Align::new(glyph_element).finish()) + .with_width(size) + .with_height(size) + .finish(); Stack::new() .with_child(disc) diff --git a/app/src/ai/blocklist/agent_view/subagent_inline_panel.rs b/app/src/ai/blocklist/agent_view/subagent_inline_panel.rs deleted file mode 100644 index acb84daa..00000000 --- a/app/src/ai/blocklist/agent_view/subagent_inline_panel.rs +++ /dev/null @@ -1,391 +0,0 @@ -//! Inline subagent panel rendered within the parent agent's chat flow. -//! -//! Shows a collapsible panel with the subagent's status, a mini-transcript of -//! recent messages, and controls to expand inline or open the full child view. - -use galaxyui::elements::{ - ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element, Empty, Flex, Hoverable, - MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, Radius, Shrinkable, Text, -}; -use galaxyui::platform::Cursor; -use galaxyui::ui_components::components::UiComponent; -use galaxyui::{AppContext, EntityId, SingletonEntity}; -use pathfinder_color::ColorU; -use warp_multi_agent_api as api; - -use crate::ai::agent::conversation::{AIConversationId, ConversationStatus, StatusColorStyle}; -use crate::ai::agent::AIAgentActionId; -use crate::ai::blocklist::agent_view::orchestration_conversation_links::dispatch_focus_or_open_child_agent_pane; -use crate::ai::blocklist::block::AIBlockAction; -use crate::ai::blocklist::inline_action::inline_action_header::{ - ICON_MARGIN, INLINE_ACTION_HEADER_VERTICAL_PADDING, INLINE_ACTION_HORIZONTAL_PADDING, -}; -use crate::ai::blocklist::inline_action::inline_action_icons::icon_size; -use crate::ai::blocklist::BlocklistAIHistoryModel; -use crate::appearance::Appearance; -use crate::ui_components::blended_colors; -use crate::ui_components::buttons::icon_button; -use crate::ui_components::icons::Icon; - -const MINI_TRANSCRIPT_MAX_LINES: usize = 8; -const MINI_TRANSCRIPT_MAX_CHARS: usize = 120; -const COMPLETION_SUMMARY_MAX_CHARS: usize = 300; -const PANEL_MAX_HEIGHT: f32 = 200.; -const PANEL_CORNER_RADIUS: f32 = 8.; - -/// State for a single subagent inline panel instance. -#[derive(Debug, Clone)] -pub struct SubagentPanelState { - pub conversation_id: AIConversationId, - pub is_expanded: bool, - pub header_mouse_state: MouseStateHandle, - pub open_mouse_state: MouseStateHandle, -} - -impl SubagentPanelState { - pub fn new(conversation_id: AIConversationId) -> Self { - Self { - conversation_id, - // The panel exists to expose the child agent's live conversation. - // Start expanded so its responses are visible without another click. - is_expanded: true, - header_mouse_state: MouseStateHandle::default(), - open_mouse_state: MouseStateHandle::default(), - } - } -} - -/// Renders the inline subagent panel for a child conversation. -pub fn render_subagent_inline_panel( - state: &SubagentPanelState, - action_id: &AIAgentActionId, - self_terminal_view_id: EntityId, - app: &AppContext, -) -> Box { - let appearance = Appearance::as_ref(app); - let theme = appearance.theme(); - - let history_model = BlocklistAIHistoryModel::as_ref(app); - let Some(conversation) = history_model.conversation(&state.conversation_id) else { - return Empty::new().finish(); - }; - - let status = conversation.status().clone(); - let agent_name = conversation.agent_name().unwrap_or("Subagent").to_string(); - - let panel_bg = blended_colors::neutral_2(theme); - - let mut column = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); - - // Header — always visible, click to toggle expand/collapse - let header_mouse_state = state.header_mouse_state.clone(); - let open_mouse_state = state.open_mouse_state.clone(); - let ui_builder = appearance.ui_builder().clone(); - let toggle_action_id = action_id.clone(); - let header_status = status.clone(); - let header_expanded = state.is_expanded; - let toggle = Hoverable::new(header_mouse_state, move |_mouse_state| { - render_panel_header(&agent_name, &header_status, header_expanded, panel_bg, app) - }) - .with_cursor(Cursor::PointingHand) - .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(AIBlockAction::ToggleSubagentPanel { - action_id: toggle_action_id.clone(), - }); - }) - .finish(); - let child_conversation_id = state.conversation_id; - let open = icon_button(appearance, Icon::LinkExternal, false, open_mouse_state) - .with_tooltip(move || { - ui_builder - .tool_tip("Open child conversation".to_string()) - .build() - .finish() - }) - .build() - .on_click(move |ctx, app, _| { - dispatch_focus_or_open_child_agent_pane( - child_conversation_id, - self_terminal_view_id, - ctx, - app, - ); - }) - .finish(); - column.add_child( - Flex::row() - .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) - .with_main_axis_size(MainAxisSize::Max) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(Shrinkable::new(1., toggle).finish()) - .with_child( - Container::new(open) - .with_padding_left(4.) - .with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING) - .finish(), - ) - .finish(), - ); - - // Body (mini-transcript) — only when expanded - if state.is_expanded { - let transcript_lines = collect_mini_transcript(&state.conversation_id, app); - if !transcript_lines.is_empty() { - column.add_child(render_mini_transcript(&transcript_lines, panel_bg, app)); - } - } - - // Footer — show summary when complete - if status.is_done() { - if let Some(summary) = get_completion_summary(&state.conversation_id, app) { - column.add_child(render_summary_footer(&summary, panel_bg, app)); - } - } - - Container::new(column.finish()) - .with_background_color(panel_bg) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(PANEL_CORNER_RADIUS))) - .with_margin_top(4.) - .with_margin_bottom(4.) - .finish() -} - -fn render_panel_header( - agent_name: &str, - status: &ConversationStatus, - is_expanded: bool, - _background: ColorU, - app: &AppContext, -) -> Box { - let appearance = Appearance::as_ref(app); - let theme = appearance.theme(); - let font_family = appearance.ui_font_family(); - let font_size = appearance.monospace_font_size(); - let surface = theme.surface_2(); - - let mut header_row = Flex::row() - .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) - .with_main_axis_size(MainAxisSize::Max) - .with_cross_axis_alignment(CrossAxisAlignment::Center); - - // Left: status icon + agent name + status text - let mut left_side = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center); - - let (icon, icon_color) = status.status_icon_and_color(theme, StatusColorStyle::Standard); - let status_icon_element = - ConstrainedBox::new(galaxyui::elements::Icon::new(icon.into(), icon_color).finish()) - .with_width(icon_size(app)) - .with_height(icon_size(app)) - .finish(); - - left_side.add_child( - Container::new(status_icon_element) - .with_margin_right(ICON_MARGIN) - .finish(), - ); - - let name_color: ColorU = theme.main_text_color(surface).into(); - left_side.add_child( - Text::new_inline(agent_name.to_string(), font_family, font_size) - .with_color(name_color) - .finish(), - ); - - let status_text = match status { - ConversationStatus::InProgress => "Working...", - ConversationStatus::Success => "Complete", - ConversationStatus::Error => "Error", - ConversationStatus::TransientError => "Retrying...", - ConversationStatus::Cancelled => "Cancelled", - ConversationStatus::Blocked { .. } => "Blocked", - ConversationStatus::WaitingForEvents => "Waiting...", - }; - let status_text_color = blended_colors::text_disabled(theme, surface); - left_side.add_child( - Container::new( - Text::new_inline(status_text.to_string(), font_family, font_size) - .with_color(status_text_color) - .finish(), - ) - .with_margin_left(8.) - .finish(), - ); - - header_row.add_child(Shrinkable::new(1.0, left_side.finish()).finish()); - - // Right: collapse/expand chevron - let mut right_side = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center); - - let chevron_icon = if is_expanded { - Icon::ChevronDown - } else { - Icon::ChevronRight - }; - let chevron_color = blended_colors::text_disabled(theme, surface); - let chevron = ConstrainedBox::new( - galaxyui::elements::Icon::new(chevron_icon.into(), chevron_color).finish(), - ) - .with_width(icon_size(app)) - .with_height(icon_size(app)) - .finish(); - right_side.add_child(Container::new(chevron).with_margin_right(4.).finish()); - - header_row.add_child(right_side.finish()); - - Container::new(header_row.finish()) - .with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_top(INLINE_ACTION_HEADER_VERTICAL_PADDING) - .with_padding_bottom(INLINE_ACTION_HEADER_VERTICAL_PADDING) - .finish() -} - -fn collect_mini_transcript(conversation_id: &AIConversationId, app: &AppContext) -> Vec { - let history_model = BlocklistAIHistoryModel::as_ref(app); - let Some(conversation) = history_model.conversation(conversation_id) else { - return vec![]; - }; - - let messages = conversation.all_linearized_messages(); - collect_visible_transcript(&messages, MINI_TRANSCRIPT_MAX_LINES) -} - -fn collect_visible_transcript(messages: &[&api::Message], max_lines: usize) -> Vec { - let mut lines = messages - .iter() - .rev() - // Filter first, then apply the visible-line limit. A tool-heavy turn can - // contain many internal messages between user/agent chat messages. - .filter_map(|message| extract_message_text(message)) - .take(max_lines) - .map(|text| truncate_with_ellipsis(&text, MINI_TRANSCRIPT_MAX_CHARS)) - .collect::>(); - lines.reverse(); - lines -} - -fn truncate_with_ellipsis(text: &str, max_chars: usize) -> String { - let mut chars = text.chars(); - let prefix = chars.by_ref().take(max_chars).collect::(); - if chars.next().is_none() { - return prefix; - } - - let visible_prefix_chars = max_chars.saturating_sub(3); - let mut truncated = prefix - .chars() - .take(visible_prefix_chars) - .collect::(); - truncated.push_str(&".".repeat(max_chars.min(3))); - truncated -} - -fn extract_message_text(msg: &api::Message) -> Option { - let message_content = msg.message.as_ref()?; - match message_content { - api::message::Message::AgentOutput(_) => extract_agent_output_text(msg), - api::message::Message::UserQuery(query) => { - if query.query.is_empty() { - None - } else { - Some(query.query.clone()) - } - } - _ => None, - } -} - -fn extract_agent_output_text(msg: &api::Message) -> Option { - let api::message::Message::AgentOutput(output) = msg.message.as_ref()? else { - return None; - }; - (!output.text.is_empty()).then(|| output.text.clone()) -} - -fn render_mini_transcript( - lines: &[String], - background: ColorU, - app: &AppContext, -) -> Box { - let appearance = Appearance::as_ref(app); - let theme = appearance.theme(); - let text_color = blended_colors::text_disabled(theme, background); - let font_family = appearance.ui_font_family(); - let font_size = appearance.monospace_font_size() - 1.; - - let mut column = Flex::column(); - for line in lines { - let prefixed = format!("> {line}"); - column.add_child( - Text::new_inline(prefixed, font_family, font_size) - .with_color(text_color) - .finish(), - ); - } - - ConstrainedBox::new( - Container::new(column.finish()) - .with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_top(4.) - .with_padding_bottom(4.) - .finish(), - ) - .with_max_height(PANEL_MAX_HEIGHT) - .finish() -} - -fn get_completion_summary(conversation_id: &AIConversationId, app: &AppContext) -> Option { - let history_model = BlocklistAIHistoryModel::as_ref(app); - let conversation = history_model.conversation(conversation_id)?; - - if !conversation.status().is_done() { - return None; - } - - completion_summary_from_messages(&conversation.all_linearized_messages()) -} - -fn completion_summary_from_messages(messages: &[&api::Message]) -> Option { - messages.iter().rev().find_map(|message| { - extract_agent_output_text(message) - .map(|text| truncate_with_ellipsis(&text, COMPLETION_SUMMARY_MAX_CHARS)) - }) -} - -fn render_summary_footer(summary: &str, _background: ColorU, app: &AppContext) -> Box { - let appearance = Appearance::as_ref(app); - let theme = appearance.theme(); - let surface = theme.surface_2(); - let text_color: ColorU = theme.main_text_color(surface).into(); - let label_color = blended_colors::text_disabled(theme, surface); - let font_family = appearance.ui_font_family(); - let font_size = appearance.monospace_font_size(); - - let mut row = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Start); - row.add_child( - Text::new_inline("Summary: ".to_string(), font_family, font_size) - .with_color(label_color) - .finish(), - ); - row.add_child( - Shrinkable::new( - 1.0, - Text::new_inline(summary.to_string(), font_family, font_size) - .with_color(text_color) - .finish(), - ) - .finish(), - ); - - Container::new(row.finish()) - .with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING) - .with_padding_top(6.) - .with_padding_bottom(6.) - .finish() -} - -#[cfg(test)] -#[path = "subagent_inline_panel_tests.rs"] -mod tests; diff --git a/app/src/ai/blocklist/agent_view/subagent_inline_panel_tests.rs b/app/src/ai/blocklist/agent_view/subagent_inline_panel_tests.rs deleted file mode 100644 index 546c269a..00000000 --- a/app/src/ai/blocklist/agent_view/subagent_inline_panel_tests.rs +++ /dev/null @@ -1,128 +0,0 @@ -use warp_multi_agent_api as api; - -use super::{ - collect_visible_transcript, completion_summary_from_messages, extract_message_text, - truncate_with_ellipsis, SubagentPanelState, -}; -use crate::ai::agent::conversation::AIConversationId; - -fn message(content: api::message::Message) -> api::Message { - api::Message { - message: Some(content), - ..Default::default() - } -} - -fn user_query(text: &str) -> api::Message { - message(api::message::Message::UserQuery(api::message::UserQuery { - query: text.to_string(), - ..Default::default() - })) -} - -fn system_query() -> api::Message { - message(api::message::Message::SystemQuery( - api::message::SystemQuery::default(), - )) -} - -fn agent_output(text: &str) -> api::Message { - message(api::message::Message::AgentOutput( - api::message::AgentOutput { - text: text.to_string(), - }, - )) -} - -fn message_refs(messages: &[api::Message]) -> Vec<&api::Message> { - messages.iter().collect() -} - -#[test] -fn truncate_with_ellipsis_preserves_short_text() { - assert_eq!( - truncate_with_ellipsis("Galaxy terminal", 20), - "Galaxy terminal" - ); -} - -#[test] -fn truncate_with_ellipsis_is_unicode_safe() { - let truncated = truncate_with_ellipsis("🚀🚀🚀🚀🚀 Galaxy", 8); - - assert_eq!(truncated.chars().count(), 8); - assert!(truncated.ends_with("...")); -} - -#[test] -fn truncate_with_ellipsis_handles_tiny_limits() { - assert_eq!(truncate_with_ellipsis("Galaxy", 2), ".."); -} - -#[test] -fn new_panel_starts_expanded_so_agent_chat_is_visible() { - let state = SubagentPanelState::new(AIConversationId::new()); - - assert!(state.is_expanded); -} - -#[test] -fn transcript_shows_user_and_agent_messages_but_hides_system_queries() { - let system = system_query(); - let user = user_query("Please check the build"); - let agent = agent_output("The build is still running."); - - assert_eq!(extract_message_text(&system), None); - assert_eq!( - extract_message_text(&user).as_deref(), - Some("Please check the build") - ); - assert_eq!( - extract_message_text(&agent).as_deref(), - Some("The build is still running.") - ); - assert_eq!(extract_message_text(&user_query("")), None); - assert_eq!(extract_message_text(&agent_output("")), None); -} - -#[test] -fn hidden_system_messages_do_not_displace_agent_responses() { - let mut messages = vec![agent_output("Visible response to a system request")]; - messages.extend((0..32).map(|_| system_query())); - let refs = message_refs(&messages); - - assert_eq!( - collect_visible_transcript(&refs, 8), - vec!["Visible response to a system request"] - ); -} - -#[test] -fn transcript_limits_visible_messages_and_keeps_chronological_order() { - let messages = (0..10) - .map(|index| agent_output(&format!("response {index}"))) - .collect::>(); - let refs = message_refs(&messages); - - assert_eq!( - collect_visible_transcript(&refs, 8), - (2..10) - .map(|index| format!("response {index}")) - .collect::>() - ); -} - -#[test] -fn completion_summary_uses_latest_agent_response() { - let messages = vec![ - agent_output("Final assistant answer"), - user_query("A trailing user message"), - system_query(), - ]; - let refs = message_refs(&messages); - - assert_eq!( - completion_summary_from_messages(&refs).as_deref(), - Some("Final assistant answer") - ); -} diff --git a/app/src/ai/blocklist/agent_view/subagent_link.rs b/app/src/ai/blocklist/agent_view/subagent_link.rs new file mode 100644 index 00000000..ab7f9855 --- /dev/null +++ b/app/src/ai/blocklist/agent_view/subagent_link.rs @@ -0,0 +1,110 @@ +//! Compact links to existing child conversations in launch and follow-up output. + +use galaxyui::elements::{ + ChildAnchor, ConstrainedBox, Container, CrossAxisAlignment, Empty, Flex, Hoverable, + MainAxisSize, MouseStateHandle, ParentAnchor, ParentElement, Text, +}; +use galaxyui::platform::Cursor; +use galaxyui::{AppContext, Element, EntityId, SingletonEntity}; +use pathfinder_geometry::vector::vec2f; + +use super::orchestration_conversation_links::{ + dispatch_focus_or_open_child_agent_pane, is_conversation_open_in_other_visible_view, +}; +use super::orchestration_pill_bar::render_static_agent_pill; +use crate::ai::agent::conversation::{AIConversationId, ConversationStatus, StatusColorStyle}; +use crate::ai::blocklist::BlocklistAIHistoryModel; +use crate::appearance::Appearance; +use crate::ui_components::blended_colors; + +#[derive(Debug, Clone)] +pub struct SubagentLinkState { + pub conversation_id: AIConversationId, + mouse_state: MouseStateHandle, +} + +impl SubagentLinkState { + pub fn new(conversation_id: AIConversationId) -> Self { + Self { + conversation_id, + mouse_state: MouseStateHandle::default(), + } + } +} + +pub fn render_subagent_link( + state: &SubagentLinkState, + self_terminal_view_id: EntityId, + app: &AppContext, +) -> Box { + let Some(conversation) = + BlocklistAIHistoryModel::as_ref(app).conversation(&state.conversation_id) + else { + return Empty::new().finish(); + }; + let appearance = Appearance::as_ref(app); + let theme = appearance.theme(); + let name = conversation + .agent_name() + .filter(|name| !name.is_empty()) + .unwrap_or("Agent"); + let status = conversation.status(); + let status_text = match status { + ConversationStatus::InProgress => "Working…", + ConversationStatus::Success => "Complete", + ConversationStatus::Error => "Error", + ConversationStatus::TransientError => "Retrying…", + ConversationStatus::Cancelled => "Cancelled", + ConversationStatus::Blocked { .. } => "Blocked", + ConversationStatus::WaitingForEvents => "Waiting…", + }; + let (icon, color) = status.status_icon_and_color(theme, StatusColorStyle::Standard); + let row = Flex::row() + .with_main_axis_size(MainAxisSize::Min) + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(8.) + .with_child(render_static_agent_pill(name, app)) + .with_child( + ConstrainedBox::new(icon.to_warpui_icon(color.into()).finish()) + .with_width(12.) + .with_height(12.) + .finish(), + ) + .with_child( + Text::new(status_text.to_string(), appearance.ui_font_family(), 12.) + .with_color(blended_colors::text_disabled(theme, theme.surface_2())) + .finish(), + ) + .finish(); + let conversation_id = state.conversation_id; + let tooltip = if is_conversation_open_in_other_visible_view( + conversation_id, + self_terminal_view_id, + app, + ) { + format!("Focus {name}'s pane") + } else { + format!("Open {name} in a new pane") + }; + let link = Hoverable::new(state.mouse_state.clone(), move |_| row) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, app, _| { + dispatch_focus_or_open_child_agent_pane( + conversation_id, + self_terminal_view_id, + ctx, + app, + ); + }) + .finish(); + Container::new(appearance.ui_builder().overlay_tool_tip_on_element( + tooltip, + state.mouse_state.clone(), + link, + ParentAnchor::TopLeft, + ChildAnchor::BottomLeft, + vec2f(0., -4.), + )) + .with_vertical_padding(4.) + .finish() +} diff --git a/app/src/ai/blocklist/block.rs b/app/src/ai/blocklist/block.rs index 94507ba5..6eddbba5 100644 --- a/app/src/ai/blocklist/block.rs +++ b/app/src/ai/blocklist/block.rs @@ -27,7 +27,7 @@ use std::sync::Arc; use ai::agent::action::{AskUserQuestionItem, InsertReviewComment, RunAgentsRequest}; use base64::Engine as _; -use chrono::Duration; +use chrono::{DateTime, Duration, Local}; use cli_controller::{CLISubagentController, CLISubagentEvent, LongRunningCommandControlState}; use find::FindState; use galaxy_agent_core::RuntimeActivityStatus; @@ -197,6 +197,7 @@ use crate::ui_components::icons::Icon; use crate::util::link_detection::*; #[cfg(feature = "local_fs")] use crate::util::openable_file_type::{is_supported_image_file, FileTarget}; +use crate::util::time_format::format_message_timestamp; use crate::view_components::action_button::{ ActionButton, ActionButtonTheme, ButtonSize, KeystrokeSource, NakedTheme, PrimaryTheme, SecondaryTheme, @@ -465,6 +466,7 @@ pub(super) struct AIBlockStateHandles { /// Mouse state handle for the overflow menu button overflow_menu_handle: MouseStateHandle, + query_timestamp_tooltip_handle: MouseStateHandle, menu_accept_button_handle: MouseStateHandle, menu_reject_button_handle: MouseStateHandle, @@ -485,8 +487,8 @@ pub(super) struct AIBlockStateHandles { /// multiple skill banners in the same block don't share hover/click state. skill_button_handles: HashMap, usage_button_handle: MouseStateHandle, - subagent_panel_states: - HashMap, + subagent_link_states: + HashMap, } #[derive(Default, Clone, Debug)] @@ -1404,13 +1406,13 @@ impl AIBlock { |me, _, event, ctx| { if me .state_handles - .subagent_panel_states + .subagent_link_states .values() .any(|state| history_event_affects_conversation(event, state.conversation_id)) { // Child conversations live on a different terminal // surface, so they bypass the parent block's normal - // terminal-surface filter. Repaint the inline transcript + // terminal-surface filter. Repaint the conversation link // and status whenever one of those children changes. ctx.notify(); } @@ -2236,12 +2238,10 @@ impl AIBlock { .map(|child| child.id()); if let Some(child_id) = child_id { self.state_handles - .subagent_panel_states + .subagent_link_states .entry(action.id.clone()) .or_insert_with(|| { - super::agent_view::subagent_inline_panel::SubagentPanelState::new( - child_id, - ) + super::agent_view::subagent_link::SubagentLinkState::new(child_id) }); } } @@ -4727,6 +4727,10 @@ impl AIBlock { self.model.status(app) } + pub fn query_sent_at(&self, app: &AppContext) -> Option> { + self.model.query_sent_at(app) + } + /// Returns `true` if this AI block contains user input. pub fn has_user_input(&self, app: &AppContext) -> bool { self.model @@ -4952,7 +4956,7 @@ impl AIBlock { }); } - // Create subagent panel state for finished StartAgent actions + // Create subagent link state for finished StartAgent actions if let Some(AIActionStatus::Finished(result)) = action_model .as_ref(ctx) @@ -4962,13 +4966,13 @@ impl AIBlock { crate::ai::agent::StartAgentResult::Success { agent_id, .. }, ) = &result.result { - if !me.state_handles.subagent_panel_states.contains_key(action_id) { + if !me.state_handles.subagent_link_states.contains_key(action_id) { if let Some(conversation_id) = crate::ai::blocklist::agent_view::orchestration_conversation_links::conversation_id_for_agent_id(agent_id, ctx) { - me.state_handles.subagent_panel_states.insert( + me.state_handles.subagent_link_states.insert( action_id.clone(), - super::agent_view::subagent_inline_panel::SubagentPanelState::new(conversation_id), + super::agent_view::subagent_link::SubagentLinkState::new(conversation_id), ); } } @@ -5117,7 +5121,7 @@ impl AIBlock { } /// Registers the direct-provider child linkage needed to render a live - /// StartAgent panel while the action is still waiting for child output. + /// StartAgent conversation link while the action is still waiting for child output. fn register_start_agent_executor_subscription( action_model: &ModelHandle, ctx: &mut ViewContext, @@ -5139,12 +5143,10 @@ impl AIBlock { } me.state_handles - .subagent_panel_states + .subagent_link_states .entry(action_id.clone()) .or_insert_with(|| { - super::agent_view::subagent_inline_panel::SubagentPanelState::new( - *child_conversation_id, - ) + super::agent_view::subagent_link::SubagentLinkState::new(*child_conversation_id) }); ctx.notify(); }); @@ -6643,6 +6645,7 @@ pub enum AIBlockAction { /// Copy the content from the previous user query. /// Note that this block may not have the user query. CopyQuery, + CopyTimestamp, /// Copy all AI output from the previous user query to the next user query. /// Note that this contains more than just this block, since from the user perspective everything after the user query appears like one block. CopyOutput, @@ -6696,9 +6699,6 @@ pub enum AIBlockAction { OpenCommentInGitHub { url: String, }, - ToggleSubagentPanel { - action_id: AIAgentActionId, - }, ToggleIsUsageFooterExpanded, } @@ -6711,12 +6711,6 @@ impl TypedActionView for AIBlock { self.is_usage_footer_expanded = !self.is_usage_footer_expanded; ctx.notify(); } - AIBlockAction::ToggleSubagentPanel { action_id } => { - if let Some(state) = self.state_handles.subagent_panel_states.get_mut(action_id) { - state.is_expanded = !state.is_expanded; - ctx.notify(); - } - } AIBlockAction::SetCollapsibleBlockPinnedToBottom { message_id, pinned_to_bottom, @@ -7119,6 +7113,14 @@ impl TypedActionView for AIBlock { ctx.clipboard() .write(ClipboardContent::plain_text(prompt_text)); } + AIBlockAction::CopyTimestamp => { + if let Some(timestamp) = self.query_sent_at(ctx) { + ctx.clipboard() + .write(ClipboardContent::plain_text(format_message_timestamp( + ×tamp, + ))); + } + } AIBlockAction::CopyOutput => { // Copy all AI output from preceding user query until the next user query let output_text = self.get_output_text_since_preceding_user_query(ctx); diff --git a/app/src/ai/blocklist/block/model.rs b/app/src/ai/blocklist/block/model.rs index aeacba0c..2ad21db3 100644 --- a/app/src/ai/blocklist/block/model.rs +++ b/app/src/ai/blocklist/block/model.rs @@ -1,7 +1,7 @@ mod helper; mod model_impl; -use chrono::TimeDelta; +use chrono::{DateTime, Local, TimeDelta}; use galaxy_core::features::FeatureFlag; use galaxyui::{AppContext, ViewContext}; pub use helper::AIBlockModelHelper; @@ -182,6 +182,9 @@ pub trait AIBlockModel { fn time_since_request_start(&self, _app: &AppContext) -> Option { None } + fn query_sent_at(&self, _app: &AppContext) -> Option> { + None + } /// Returns the [`LLMId`] for the base model used to generate output in this block. fn base_model<'a>(&'a self, app: &'a AppContext) -> Option<&'a LLMId>; diff --git a/app/src/ai/blocklist/block/model/model_impl.rs b/app/src/ai/blocklist/block/model/model_impl.rs index ef9ebbb4..ac3c16fe 100644 --- a/app/src/ai/blocklist/block/model/model_impl.rs +++ b/app/src/ai/blocklist/block/model/model_impl.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use anyhow::{anyhow, Result}; -use chrono::{Local, TimeDelta}; +use chrono::{DateTime, Local, TimeDelta}; use galaxyui::{AppContext, Entity, SingletonEntity, View, ViewContext}; use history_model::{BlocklistAIHistoryEvent, BlocklistAIHistoryModel}; use session_sharing_protocol::common::ParticipantId; @@ -15,6 +15,7 @@ use crate::ai::agent::{ use crate::ai::blocklist::history_model; use crate::ai::blocklist::model::{AIRequestType, PassiveRequestType}; use crate::ai::llms::LLMId; +use crate::util::time_format::is_trustworthy_message_timestamp; /// Standard [`AIBlock`] impl for live outputs corresponding to an `OutputStream`. pub struct AIBlockModelImpl { @@ -146,6 +147,13 @@ where } } + fn query_sent_at(&self, app: &AppContext) -> Option> { + self.exchange(app) + .ok() + .map(|exchange| exchange.start_time) + .filter(is_trustworthy_message_timestamp) + } + fn base_model<'a>(&'a self, app: &'a AppContext) -> Option<&'a LLMId> { let exchange = self.exchange(app); match exchange { diff --git a/app/src/ai/blocklist/block/view_impl.rs b/app/src/ai/blocklist/block/view_impl.rs index 04f937cc..e2c52766 100644 --- a/app/src/ai/blocklist/block/view_impl.rs +++ b/app/src/ai/blocklist/block/view_impl.rs @@ -1004,6 +1004,10 @@ impl View for AIBlock { user_display_name: &avatar_display_name, profile_image_path: profile_image_path.as_ref(), avatar_color, + query_sent_at: self.query_sent_at(app), + query_timestamp_tooltip_handle: &self + .state_handles + .query_timestamp_tooltip_handle, query_and_index: Some((&query_for_display, input_index)), query_prefix_highlight_len, detected_links_state: &self.detected_links_state, diff --git a/app/src/ai/blocklist/block/view_impl/orchestration.rs b/app/src/ai/blocklist/block/view_impl/orchestration.rs index d83d47fb..27838784 100644 --- a/app/src/ai/blocklist/block/view_impl/orchestration.rs +++ b/app/src/ai/blocklist/block/view_impl/orchestration.rs @@ -1,13 +1,16 @@ //! Rendering functions for orchestration-related output items (messaging & agent management). use galaxyui::elements::{ - ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Empty, Flex, FormattedTextElement, - Hoverable, MouseStateHandle, ParentElement, Radius, Shrinkable, Text, + ChildAnchor, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Empty, Flex, + FormattedTextElement, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, + ParentElement, ParentOffsetBounds, Radius, Shrinkable, Stack, Text, }; use galaxyui::platform::Cursor; +use galaxyui::ui_components::components::UiComponent; use galaxyui::{AppContext, Element, SingletonEntity}; use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; use pathfinder_color::ColorU; +use pathfinder_geometry::vector::vec2f; use super::common::render_scrollable_collapsible_content; use super::output::{action_icon, Props}; @@ -24,6 +27,7 @@ use crate::ai::blocklist::agent_view::orchestration_avatar::OrchestrationAvatar; use crate::ai::blocklist::agent_view::orchestration_conversation_links::{ conversation_id_for_agent_id, conversation_id_for_agent_id_in_orchestration, conversation_navigation_card_with_icon, dispatch_focus_or_open_child_agent_pane, + is_conversation_open_in_other_visible_view, }; use crate::ai::blocklist::block::model::AIBlockModelHelper; use crate::ai::blocklist::block::{ @@ -197,6 +201,20 @@ fn transcript_metadata(recipients: &[OrchestrationParticipant], subject: &str) - } } +/// Hover tooltip copy for the clickable child-agent avatar in an +/// orchestration transcript row. The avatar is a bare letter disc, so +/// without this the click affordance (open/focus the child's pane) is +/// undiscoverable. Wording mirrors the pill bar's overflow-menu copy +/// ("Open in new pane" / "Focus pane") so the same navigation reads the +/// same way everywhere. +fn transcript_avatar_tooltip(display_name: &str, is_open_in_other_pane: bool) -> String { + if is_open_in_other_pane { + format!("Focus {display_name}'s pane") + } else { + format!("Open {display_name} in a new pane") + } +} + struct TranscriptRowData<'a> { participant: &'a OrchestrationParticipant, recipients: &'a [OrchestrationParticipant], @@ -330,17 +348,40 @@ fn render_transcript_row( // open a new pane. let mouse_state = mouse_state.clone(); let self_terminal_view_id = props.terminal_view_id; - Hoverable::new(mouse_state, move |_| avatar) - .with_cursor(Cursor::PointingHand) - .on_click(move |ctx, app, _| { - dispatch_focus_or_open_child_agent_pane( - conversation_id, - self_terminal_view_id, - ctx, - app, + let tooltip_label = transcript_avatar_tooltip( + &data.participant.display_name, + is_conversation_open_in_other_visible_view(conversation_id, self_terminal_view_id, app), + ); + let ui_builder = appearance.ui_builder().clone(); + Hoverable::new(mouse_state, move |state| { + // Tooltip overlay, positioned above the avatar on hover. Same + // pattern as `render_force_refresh_inline` in `common.rs`; the + // overlay layer keeps it from being clipped by the scrollable + // transcript content. + let mut stack = Stack::new().with_child(avatar); + if state.is_hovered() { + stack.add_positioned_overlay_child( + ui_builder.tool_tip(tooltip_label).build().finish(), + OffsetPositioning::offset_from_parent( + vec2f(0., -4.), + ParentOffsetBounds::WindowByPosition, + ParentAnchor::TopLeft, + ChildAnchor::BottomLeft, + ), ); - }) - .finish() + } + stack.finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, app, _| { + dispatch_focus_or_open_child_agent_pane( + conversation_id, + self_terminal_view_id, + ctx, + app, + ); + }) + .finish() } else { avatar }; @@ -448,16 +489,15 @@ pub(super) fn render_send_message( ) -> Box { let message_row = render_send_message_body(props, action_id, address, subject, message, message_id, app); - let Some(panel) = props.state_handles.subagent_panel_states.get(action_id) else { + let Some(panel) = props.state_handles.subagent_link_states.get(action_id) else { return message_row; }; Flex::column() .with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_child(message_row) .with_child( - crate::ai::blocklist::agent_view::subagent_inline_panel::render_subagent_inline_panel( + crate::ai::blocklist::agent_view::subagent_link::render_subagent_link( panel, - action_id, props.terminal_view_id, app, ) @@ -716,11 +756,10 @@ pub(super) fn render_start_agent( column.add_child(body); } } - if let Some(panel_state) = props.state_handles.subagent_panel_states.get(action_id) { + if let Some(panel_state) = props.state_handles.subagent_link_states.get(action_id) { column.add_child( - crate::ai::blocklist::agent_view::subagent_inline_panel::render_subagent_inline_panel( + crate::ai::blocklist::agent_view::subagent_link::render_subagent_link( panel_state, - action_id, props.terminal_view_id, app, ), @@ -810,11 +849,10 @@ pub(super) fn render_start_agent( column.add_child(body); } } - if let Some(panel_state) = props.state_handles.subagent_panel_states.get(action_id) { + if let Some(panel_state) = props.state_handles.subagent_link_states.get(action_id) { column.add_child( - crate::ai::blocklist::agent_view::subagent_inline_panel::render_subagent_inline_panel( + crate::ai::blocklist::agent_view::subagent_link::render_subagent_link( panel_state, - action_id, props.terminal_view_id, app, ), diff --git a/app/src/ai/blocklist/block/view_impl/orchestration_tests.rs b/app/src/ai/blocklist/block/view_impl/orchestration_tests.rs index 704ce9ac..b6a856db 100644 --- a/app/src/ai/blocklist/block/view_impl/orchestration_tests.rs +++ b/app/src/ai/blocklist/block/view_impl/orchestration_tests.rs @@ -8,7 +8,8 @@ use super::{ agent_display_name_from_id, child_conversation_card_data_for_result, participant_for_agent_id, render_conversation_navigation_card_row, start_agent_cancelled_prefix, start_agent_error_prefix, start_agent_in_progress_prefix, start_agent_success_suffix, - transcript_metadata, ChildConversationCardData, OrchestrationAvatar, OrchestrationParticipant, + transcript_avatar_tooltip, transcript_metadata, ChildConversationCardData, OrchestrationAvatar, + OrchestrationParticipant, }; use crate::ai::agent::conversation::{AIConversationId, ConversationStatus}; use crate::ai::agent::{StartAgentExecutionMode, StartAgentResult}; @@ -440,6 +441,27 @@ fn participant_for_restored_child_run_id_resolves_to_agent_name() { }); } +/// The clickable letter avatar is otherwise indistinguishable from +/// decoration, so the hover tooltip must name the child agent and the +/// action taken on click. +#[test] +fn transcript_avatar_tooltip_describes_opening_a_new_pane() { + assert_eq!( + transcript_avatar_tooltip("Scout", false), + "Open Scout in a new pane" + ); +} + +/// Clicking the avatar focuses the existing pane when the child is +/// already open elsewhere, so the tooltip must not promise a new pane. +#[test] +fn transcript_avatar_tooltip_describes_focusing_an_open_pane() { + assert_eq!( + transcript_avatar_tooltip("Scout", true), + "Focus Scout's pane" + ); +} + #[test] fn transcript_metadata_uses_transcript_copy_without_technical_labels() { let recipients = vec![OrchestrationParticipant { diff --git a/app/src/ai/blocklist/block/view_impl/output.rs b/app/src/ai/blocklist/block/view_impl/output.rs index b6e85f2b..3ef228b1 100644 --- a/app/src/ai/blocklist/block/view_impl/output.rs +++ b/app/src/ai/blocklist/block/view_impl/output.rs @@ -3879,7 +3879,6 @@ fn render_collapsible_text_block_section( let appearance = Appearance::as_ref(app); let theme = appearance.theme(); let text_color = blended_colors::text_disabled(theme, theme.surface_2()); - let selectable = false; let is_streaming = props.model.status(app).is_streaming(); let mut container = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); @@ -3912,7 +3911,7 @@ fn render_collapsible_text_block_section( starting_image_section_index: &mut image_section_index, sections, text_color, - selectable, + selectable: true, find_context: props.find_context, current_working_directory: props.current_working_directory, shell_launch_data: props.shell_launch_data, diff --git a/app/src/ai/blocklist/block/view_impl/query.rs b/app/src/ai/blocklist/block/view_impl/query.rs index c09a53e5..b32bdcea 100644 --- a/app/src/ai/blocklist/block/view_impl/query.rs +++ b/app/src/ai/blocklist/block/view_impl/query.rs @@ -2,12 +2,15 @@ //! //! Queries are not rendered in blocks corresponding to requested command or requested action responses. +use chrono::{DateTime, Local}; use galaxy_core::features::FeatureFlag; use galaxy_core::ui::theme::color::internal_colors; use pathfinder_color::ColorU; +use pathfinder_geometry::vector::vec2f; use warpui::elements::{ - Container, CornerRadius, DispatchEventResult, EventHandler, Flex, MainAxisAlignment, - MainAxisSize, ParentElement, Radius, Shrinkable, Wrap, + ChildAnchor, Container, CornerRadius, DispatchEventResult, EventHandler, Flex, + MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentAnchor, ParentElement, Radius, + Shrinkable, Wrap, }; use warpui::fonts::{Properties, Style, Weight}; use warpui::ui_components::chip::Chip; @@ -21,6 +24,7 @@ use crate::ai::blocklist::AttachmentType; use crate::appearance::Appearance; use crate::ui_components::blended_colors; use crate::ui_components::icons::Icon; +use crate::util::time_format::format_message_timestamp; /// Data required to render the AI block query component. #[derive(Copy, Clone, Debug)] @@ -28,6 +32,8 @@ pub(super) struct Props<'a> { pub(super) user_display_name: &'a String, pub(super) profile_image_path: Option<&'a String>, pub(super) avatar_color: Option, + pub(super) query_sent_at: Option>, + pub(super) query_timestamp_tooltip_handle: &'a MouseStateHandle, pub(super) query_and_index: Option<(&'a str, usize)>, pub(super) query_prefix_highlight_len: Option, pub(super) detected_links_state: &'a DetectedLinksState, @@ -45,6 +51,8 @@ pub(super) fn maybe_render(props: Props, app: &AppContext) -> Option, avatar_color: Option, + query_sent_at: Option>, + query_timestamp_tooltip_handle: Option, detected_links_state: &DetectedLinksState, secret_redaction_state: &SecretRedactionState, input_index: usize, @@ -74,14 +84,29 @@ pub(crate) fn render_query( find_context: Option, app: &AppContext, ) -> Box { + let appearance = Appearance::as_ref(app); let avatar = Container::new(render_user_avatar( user_display_name, profile_image_path, avatar_color, app, )) - .with_margin_right(16.) .finish(); + let avatar = if let Some((timestamp, tooltip_handle)) = + query_sent_at.zip(query_timestamp_tooltip_handle) + { + appearance.ui_builder().overlay_tool_tip_on_element( + format!("Message sent {}", format_message_timestamp(×tamp)), + tooltip_handle, + avatar, + ParentAnchor::TopLeft, + ChildAnchor::BottomLeft, + vec2f(0., -8.), + ) + } else { + avatar + }; + let avatar = Container::new(avatar).with_margin_right(16.).finish(); let properties = Properties { style: Style::Normal, @@ -103,7 +128,6 @@ pub(crate) fn render_query( app, ); - let appearance = Appearance::as_ref(app); let mut query = Flex::column().with_child(text_element.finish()); if FeatureFlag::ImageAsContext.is_enabled() { diff --git a/app/src/ai/blocklist/history_model.rs b/app/src/ai/blocklist/history_model.rs index 0fbca161..96e4a704 100644 --- a/app/src/ai/blocklist/history_model.rs +++ b/app/src/ai/blocklist/history_model.rs @@ -2167,6 +2167,26 @@ impl BlocklistAIHistoryModel { ); } + /// Clears a closed surface without notifying a controller whose conversations moved elsewhere. + pub(crate) fn clear_conversations_for_closed_terminal_surface( + &mut self, + terminal_surface_id: EntityId, + ctx: &mut ModelContext, + ) { + if self + .live_conversation_ids_for_terminal_surface + .get(&terminal_surface_id) + .is_none_or(Vec::is_empty) + { + self.active_conversation_for_terminal_surface + .remove(&terminal_surface_id); + self.live_conversation_ids_for_terminal_surface + .remove(&terminal_surface_id); + return; + } + self.clear_conversations_for_terminal_surface(terminal_surface_id, ctx); + } + /// Handle removing a conversation from the history model, blocklist and in-memory. pub fn remove_conversation( &mut self, diff --git a/app/src/ai/blocklist/history_model_tests.rs b/app/src/ai/blocklist/history_model_tests.rs index c2c69a9b..91e42092 100644 --- a/app/src/ai/blocklist/history_model_tests.rs +++ b/app/src/ai/blocklist/history_model_tests.rs @@ -2267,6 +2267,87 @@ fn test_restore_conversations_dedup_children_by_parent() { }); } +#[test] +fn closed_surface_cleanup_preserves_transferred_conversation() { + App::test((), |mut app| async move { + let old_surface = EntityId::new(); + let new_surface = EntityId::new(); + let conversation = AIConversation::new(false, false); + let conversation_id = conversation.id(); + let history = app.add_singleton_model(|_| BlocklistAIHistoryModel::new_for_test()); + history.update(&mut app, |model, ctx| { + model.restore_conversations(new_surface, vec![conversation], ctx); + model.set_active_conversation_id(conversation_id, new_surface, ctx); + model + .active_conversation_for_terminal_surface + .insert(old_surface, conversation_id); + model + .live_conversation_ids_for_terminal_surface + .insert(old_surface, Vec::new()); + }); + + let events = Arc::new(Mutex::new(Vec::new())); + app.update(|ctx| { + let events = events.clone(); + ctx.subscribe_to_model(&history, move |_, event, _| { + events.lock().unwrap().push(event.clone()); + }); + }); + history.update(&mut app, |model, ctx| { + model.clear_conversations_for_closed_terminal_surface(old_surface, ctx); + }); + + history.read(&app, |model, _| { + assert_eq!( + model + .active_conversation_for_terminal_surface + .get(&old_surface), + None + ); + assert!(!model + .live_conversation_ids_for_terminal_surface + .contains_key(&old_surface)); + assert_eq!( + model.active_conversation_id(new_surface), + Some(conversation_id) + ); + assert_eq!( + model.terminal_surface_id_for_conversation(&conversation_id), + Some(new_surface) + ); + }); + assert!(events.lock().unwrap().is_empty()); + }); +} + +#[test] +fn closed_surface_cleanup_clears_owned_conversations() { + App::test((), |mut app| async move { + let surface_id = EntityId::new(); + let conversation = AIConversation::new(false, false); + let conversation_id = conversation.id(); + let history = app.add_singleton_model(|_| BlocklistAIHistoryModel::new_for_test()); + history.update(&mut app, |model, ctx| { + model.restore_conversations(surface_id, vec![conversation], ctx); + model.set_active_conversation_id(conversation_id, surface_id, ctx); + model.clear_conversations_for_closed_terminal_surface(surface_id, ctx); + }); + history.read(&app, |model, _| { + assert_eq!(model.active_conversation_id(surface_id), None); + assert_eq!( + model.terminal_surface_id_for_conversation(&conversation_id), + None + ); + assert_eq!( + model + .cleared_conversation_ids_for_terminal_surface + .get(&surface_id), + Some(&vec![conversation_id]) + ); + }); + }); +} + #[test] fn test_all_cleared_conversations_includes_terminal_view_id() { App::test((), |mut app| async move { diff --git a/app/src/ai/chatgpt_auth.rs b/app/src/ai/chatgpt_auth.rs index 4d989f1a..ce5a746b 100644 --- a/app/src/ai/chatgpt_auth.rs +++ b/app/src/ai/chatgpt_auth.rs @@ -61,6 +61,13 @@ impl ChatGPTAuthModel { Self { state } } + #[cfg(test)] + pub(crate) fn new_for_test() -> Self { + Self { + state: ChatGPTAuthState::NotConnected, + } + } + pub(crate) fn state(&self) -> &ChatGPTAuthState { &self.state } diff --git a/app/src/app_menus.rs b/app/src/app_menus.rs index 132bbdb6..ec716def 100644 --- a/app/src/app_menus.rs +++ b/app/src/app_menus.rs @@ -978,7 +978,17 @@ fn make_new_elements_menu_items(ctx: &AppContext) -> Vec { MenuItem::Custom(CustomMenuItem::new( "New Window", open_new_window, - no_updates, + move |_props: &MenuItemProperties, ctx: &mut AppContext| { + let mut changes = MenuItemPropertyChanges::default(); + let trigger = Trigger::Custom(CustomAction::AddWindow.into()); + let binding = ctx + .get_key_bindings() + .find(|b| b.trigger == &trigger || b.original_trigger == Some(&trigger)); + if let Some(binding) = binding { + changes.keystroke = Some(bindings::trigger_to_keystroke(binding.trigger)); + } + changes + }, Some(Keystroke::parse("cmd-n").expect("Valid keystroke")), )), MenuItem::Custom(CustomMenuItem::new( diff --git a/app/src/code_review/code_review_view.rs b/app/src/code_review/code_review_view.rs index ab505c49..e95aaf8c 100644 --- a/app/src/code_review/code_review_view.rs +++ b/app/src/code_review/code_review_view.rs @@ -7655,15 +7655,17 @@ impl TypedActionView for CodeReviewView { ctx, ); } - } else { - let file_path = self.discard_dialog_state.discard_file_paths[0].clone(); - if let Some(standardized_path) = self.to_standardized_path(&file_path) { - self.discard_file( - standardized_path, - self.discard_dialog_state.stash_changes_enabled, - ctx, - ); - } + } else if let Some(standardized_path) = self + .discard_dialog_state + .discard_file_paths + .first() + .and_then(|file_path| self.to_standardized_path(file_path)) + { + self.discard_file( + standardized_path, + self.discard_dialog_state.stash_changes_enabled, + ctx, + ); } self.discard_dialog_state.show_discard_confirm_dialog = false; self.discard_dialog_state.discard_file_paths.clear(); diff --git a/app/src/code_review/code_review_view_tests.rs b/app/src/code_review/code_review_view_tests.rs index 3680c22e..38ab850a 100644 --- a/app/src/code_review/code_review_view_tests.rs +++ b/app/src/code_review/code_review_view_tests.rs @@ -852,6 +852,24 @@ fn test_setup_dropdown_without_branches_only_has_uncommitted_changes() { }); } +#[test] +fn test_duplicate_single_file_discard_confirmation_is_ignored() { + App::test((), |mut app| async move { + let ctx = TestContext::new(&mut app, "test.txt", "line 1\nline 2\nline 3"); + + ctx.code_review_view.update(&mut app, |view, view_ctx| { + view.discard_dialog_state.operation_type = DiscardOperationType::FileUncommittedChanges; + view.discard_dialog_state.show_discard_confirm_dialog = false; + view.discard_dialog_state.discard_file_paths.clear(); + + view.handle_action(&CodeReviewAction::ConfirmDiscardFile, view_ctx); + + assert!(!view.discard_dialog_state.show_discard_confirm_dialog); + assert!(view.discard_dialog_state.discard_file_paths.is_empty()); + }); + }); +} + #[test] fn test_on_close_then_on_open_reinitializes_repo_state() { App::test((), |mut app| async move { diff --git a/app/src/pane_group/mod.rs b/app/src/pane_group/mod.rs index f91c1181..c74ad55f 100644 --- a/app/src/pane_group/mod.rs +++ b/app/src/pane_group/mod.rs @@ -3167,14 +3167,21 @@ impl PaneGroup { pane_group } - /// Returns the terminal view currently owning `conversation_id`, even if - /// that owner lives outside this pane group. + /// Returns the conversation's owner, excluding panes detached for undo-close. fn terminal_view_id_for_owned_conversation( &self, conversation_id: AIConversationId, ctx: &AppContext, ) -> Option { - BlocklistAIHistoryModel::as_ref(ctx).terminal_surface_id_for_conversation(&conversation_id) + BlocklistAIHistoryModel::as_ref(ctx) + .terminal_surface_id_for_conversation(&conversation_id) + .filter(|terminal_view_id| { + // Undo-close retains views and their conversations after their panes detach. + self.find_pane_id_for_terminal_view(*terminal_view_id, ctx) + .is_some_and(|pane_id| !self.is_pane_hidden_for_close(pane_id)) + || ActiveAgentViewsModel::as_ref(ctx) + .is_terminal_view_attached(*terminal_view_id, ctx) + }) } fn pane_id_for_owned_conversation( @@ -4619,6 +4626,8 @@ impl PaneGroup { ctx: &mut ViewContext, ) -> bool { let tracked_child_pane = self.child_agent_panes.remove(&conversation_id); + self.pending_remote_child_hydrations + .retain(|_, child_id| *child_id != conversation_id); let split_off_child_pane = self.child_agent_origin.as_ref().and_then(|origin| { (origin.conversation_id == conversation_id) .then(|| self.pane_id_for_conversation_owner(conversation_id, ctx)) @@ -6993,7 +7002,8 @@ impl PaneGroup { if let Some(owner_view_id) = BlocklistAIHistoryModel::as_ref(ctx) .terminal_surface_id_for_conversation(&conversation_id) { - ctx.dispatch_typed_action(&WorkspaceAction::FocusTerminalViewInWorkspace { + // Workspace navigation reads this pane group, so it must wait until we return. + ctx.dispatch_typed_action_deferred(WorkspaceAction::FocusTerminalViewInWorkspace { terminal_view_id: owner_view_id, }); return; @@ -7558,6 +7568,7 @@ impl PaneGroup { /// Reattach all panes to this group. This is called when a closed tab is restored. pub fn reattach_panes(&mut self, ctx: &mut ViewContext) { + self.remove_transferred_child_agent_panes(ctx); let pane_ids = self.pane_contents.keys().copied().collect_vec(); for pane_id in pane_ids { let Some(pane) = self.pane_contents.get(&pane_id) else { @@ -7568,6 +7579,23 @@ impl PaneGroup { } } + fn remove_transferred_child_agent_panes(&mut self, ctx: &mut ViewContext) { + let transferred_children = self + .child_agent_panes + .iter() + .filter_map(|(conversation_id, pane_id)| { + let owner = BlocklistAIHistoryModel::as_ref(ctx) + .terminal_surface_id_for_conversation(conversation_id)?; + let terminal_view = self.terminal_view_from_pane_id(*pane_id, ctx)?; + (owner != terminal_view.id()).then_some(*conversation_id) + }) + .collect_vec(); + + for conversation_id in transferred_children { + self.discard_child_agent_pane_for_conversation(conversation_id, ctx); + } + } + /// Attempts to attach a pane, calling pre_attach first. /// Returns true if attachment succeeded, false if pre_attach prevented it. fn try_attach_pane(&self, pane: &dyn AnyPaneContent, ctx: &mut ViewContext) -> bool { diff --git a/app/src/pane_group/mod_tests.rs b/app/src/pane_group/mod_tests.rs index 898e09ca..b7958f2a 100644 --- a/app/src/pane_group/mod_tests.rs +++ b/app/src/pane_group/mod_tests.rs @@ -2098,6 +2098,102 @@ fn test_ensure_hidden_child_agent_pane_skips_child_owned_by_another_pane_group() }); } +#[test] +fn test_ensure_hidden_child_agent_pane_restores_child_from_detached_group() { + let _agent_view = FeatureFlag::AgentView.override_enabled(true); + + App::test((), |mut app| async move { + initialize_app(&mut app); + let closed_pane_group = mock_pane_group(&mut app, Default::default()); + let reopened_pane_group = mock_pane_group(&mut app, Default::default()); + + let (parent_conversation, child_conversation_id, previous_owner) = closed_pane_group + .update(&mut app, |panes, ctx| { + let parent_pane_id = panes.focused_pane_id(ctx); + let parent_conversation_id = start_parent_conversation(panes, parent_pane_id, ctx); + let mut child = AIConversation::new(false, false); + child.set_parent_conversation_id(parent_conversation_id); + child.set_agent_name("architect".to_string()); + let child_conversation_id = child.id(); + panes.create_hidden_child_agent_pane(child, parent_pane_id, ctx); + + let previous_owner = panes + .terminal_view_from_pane_id( + panes.child_agent_panes[&child_conversation_id], + ctx, + ) + .unwrap(); + let parent_conversation = BlocklistAIHistoryModel::as_ref(ctx) + .conversation(&parent_conversation_id) + .unwrap() + .clone(); + panes.swap_active_pane_to_conversation(parent_pane_id, child_conversation_id, ctx); + panes.detach_panes(ctx); + + (parent_conversation, child_conversation_id, previous_owner) + }); + + let restored_child = reopened_pane_group.update(&mut app, |panes, ctx| { + let parent_pane_id = panes.focused_pane_id(ctx); + let parent_view = panes + .terminal_view_from_pane_id(parent_pane_id, ctx) + .unwrap(); + let parent_conversation_id = + restore_conversation_for_terminal_view(parent_view.id(), parent_conversation, ctx); + BlocklistAIHistoryModel::handle(ctx).update(ctx, |history, ctx| { + history.set_active_conversation_id(parent_conversation_id, parent_view.id(), ctx); + }); + + assert!( + panes.ensure_hidden_child_agent_pane_for_conversation(child_conversation_id, ctx) + ); + let child_pane_id = *panes + .child_agent_panes + .get(&child_conversation_id) + .expect("an undo-retained owner must not prevent restoring the child"); + panes.swap_active_pane_to_conversation(parent_pane_id, child_conversation_id, ctx); + + assert_eq!(panes.focused_pane_id(ctx), child_pane_id); + let child_view = panes + .terminal_view_from_pane_id(child_pane_id, ctx) + .unwrap(); + assert_eq!( + child_view.as_ref(ctx).active_conversation_id(ctx), + Some(child_conversation_id) + ); + child_view + }); + + assert_ne!(restored_child.id(), previous_owner.id()); + closed_pane_group.update(&mut app, |panes, ctx| { + panes.reattach_panes(ctx); + assert!(!panes.child_agent_panes.contains_key(&child_conversation_id)); + assert_eq!( + panes.find_pane_id_for_terminal_view(previous_owner.id(), ctx), + None + ); + assert_eq!(panes.visible_pane_count(), 1); + assert!( + panes.ensure_hidden_child_agent_pane_for_conversation(child_conversation_id, ctx) + ); + }); + closed_pane_group.update(&mut app, |panes, ctx| { + panes.clean_up_panes(ctx); + }); + app.read(|ctx| { + assert_eq!( + BlocklistAIHistoryModel::as_ref(ctx) + .terminal_surface_id_for_conversation(&child_conversation_id), + Some(restored_child.id()) + ); + assert_eq!( + restored_child.as_ref(ctx).active_conversation_id(ctx), + Some(child_conversation_id) + ); + }); + }); +} + #[test] fn test_entering_parent_agent_view_skips_child_owned_by_another_pane_group() { let _agent_view = FeatureFlag::AgentView.override_enabled(true); diff --git a/app/src/pane_group/pane/terminal_pane.rs b/app/src/pane_group/pane/terminal_pane.rs index 6d096a6d..cc3234a8 100644 --- a/app/src/pane_group/pane/terminal_pane.rs +++ b/app/src/pane_group/pane/terminal_pane.rs @@ -423,8 +423,10 @@ impl PaneContent for TerminalPane { // Only immediately clear conversations and delete blocks if the session is being // permanently closed. BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, ctx| { - history_model - .clear_conversations_for_terminal_surface(self.terminal_view(ctx).id(), ctx); + history_model.clear_conversations_for_closed_terminal_surface( + self.terminal_view(ctx).id(), + ctx, + ); }); self.delete_blocks(ctx); } diff --git a/app/src/resource_center/utils.rs b/app/src/resource_center/utils.rs index ea29f92c..01263e92 100644 --- a/app/src/resource_center/utils.rs +++ b/app/src/resource_center/utils.rs @@ -117,11 +117,6 @@ pub const FUNDAMENTALS_KEYBINDINGS: &[&str] = &[ /// anywhere else in the code. pub fn get_additional_keybindings() -> Vec { vec![ - CommandBinding::new( - "workspace:new_window".into(), - "Open New Window".into(), - Some(Keystroke::parse("cmd-n").expect("Valid keystroke")), - ), CommandBinding::new( "workspace:hide_warp".into(), "Hide Galaxy".into(), diff --git a/app/src/settings_view/about_page.rs b/app/src/settings_view/about_page.rs index dc43dbd7..712ad196 100644 --- a/app/src/settings_view/about_page.rs +++ b/app/src/settings_view/about_page.rs @@ -5,7 +5,9 @@ use galaxyui::elements::{ }; use galaxyui::fonts::Weight; use galaxyui::text_layout::TextAlignment; -use galaxyui::{AppContext, Entity, SingletonEntity, View, ViewContext, ViewHandle}; +use galaxyui::{ + AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, +}; use warpui::assets::asset_cache::AssetSource; use super::settings_page::{ @@ -31,6 +33,9 @@ impl AboutPageView { impl Entity for AboutPageView { type Event = SettingsPageEvent; } +impl TypedActionView for AboutPageView { + type Action = (); +} impl View for AboutPageView { fn ui_name() -> &'static str { diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index cc54e032..91a9fedf 100644 --- a/app/src/settings_view/mod.rs +++ b/app/src/settings_view/mod.rs @@ -1175,7 +1175,7 @@ impl SettingsView { }); // About page - let about_page_handle = ctx.add_view(AboutPageView::new); + let about_page_handle = ctx.add_typed_action_view(AboutPageView::new); // AI page let ai_page_handle = ctx.add_typed_action_view(AISettingsPageView::new); diff --git a/app/src/terminal/view.rs b/app/src/terminal/view.rs index e813367c..d95c966e 100644 --- a/app/src/terminal/view.rs +++ b/app/src/terminal/view.rs @@ -530,6 +530,7 @@ use crate::workspace::view::cloud_agent_capacity_modal::CloudAgentCapacityModalV use crate::workspace::{ CommandSearchOptions, ForkAIConversationParams, ForkFromExchange, ForkedConversationDestination, OneTimeModalModel, ToastStack, WorkspaceAction, + WorkspaceRegistry, }; use crate::workspaces::user_workspaces::{UserWorkspaces, UserWorkspacesEvent}; use crate::workspaces::workspace::CustomerType; @@ -1428,6 +1429,9 @@ pub enum ContextMenuAction { CopyAIBlockQuery { ai_block_view_id: EntityId, }, + CopyAIBlockTimestamp { + ai_block_view_id: EntityId, + }, /// Copy the AI block output text CopyAIBlockOutput { ai_block_view_id: EntityId, @@ -1528,6 +1532,7 @@ impl fmt::Debug for ContextMenuAction { StopSharing => f.write_str("StopSharing"), CopyAIDebuggingLink { .. } => f.write_str("CopyAIDebuggingLink"), CopyAIBlockQuery { .. } => f.write_str("CopyAIBlockPrompt"), + CopyAIBlockTimestamp { .. } => f.write_str("CopyAIBlockTimestamp"), CopyAIBlockOutput { .. } => f.write_str("CopyAIBlockOutput"), CopyAIBlock { .. } => f.write_str("CopyAIBlockBoth"), CopyAIBlockConversation { .. } => f.write_str("CopyAIBlockConversation"), @@ -12149,7 +12154,17 @@ impl TerminalView { // case, we want the block to be focused because otherwise, // users get stuck as they'd otherwise need to click into the // box to respond to whether or not they want to update oh my zsh. - self.focus_terminal(ctx); + // + // Skipped while a tab or tab-group rename editor is focused. Taking focus + // would end the rename and lose user inputs (#14241). + let inline_rename_editor_is_focused = WorkspaceRegistry::as_ref(ctx) + .get(self.window_id, ctx) + .is_some_and(|workspace| { + workspace.as_ref(ctx).is_inline_rename_editor_focused(ctx) + }); + if !inline_rename_editor_is_focused { + self.focus_terminal(ctx); + } } ModelEvent::AfterBlockStarted { command, @@ -24998,6 +25013,19 @@ impl TerminalView { } } } + CopyAIBlockTimestamp { ai_block_view_id } => { + for rich_content in self.rich_content_views.iter() { + if let Some(ai_metadata) = rich_content + .ai_block_metadata() + .filter(|metadata| metadata.ai_block_handle.id() == *ai_block_view_id) + { + ai_metadata.ai_block_handle.update(ctx, |block, ctx| { + block.handle_action(&AIBlockAction::CopyTimestamp, ctx); + }); + break; + } + } + } CopyAIBlockOutput { ai_block_view_id } => { // Copy only the current AI block's output for rich_content in self.rich_content_views.iter() { diff --git a/app/src/terminal/view/ambient_agent/block/query.rs b/app/src/terminal/view/ambient_agent/block/query.rs index 4a2f00d3..b686027a 100644 --- a/app/src/terminal/view/ambient_agent/block/query.rs +++ b/app/src/terminal/view/ambient_agent/block/query.rs @@ -74,6 +74,8 @@ fn render_user_query( &user_display_name, profile_image_url.as_ref(), None, + None, + None, &Default::default(), &Default::default(), 0, diff --git a/app/src/terminal/view/context_menu.rs b/app/src/terminal/view/context_menu.rs index 292b6099..7207e7a3 100644 --- a/app/src/terminal/view/context_menu.rs +++ b/app/src/terminal/view/context_menu.rs @@ -119,6 +119,27 @@ impl TerminalView { .into_item(), ); } + let has_query_timestamp = self.rich_content_views.iter().any(|rich_content| { + rich_content + .ai_block_metadata() + .filter(|metadata| metadata.ai_block_handle.id() == ai_block_view_id) + .is_some_and(|metadata| { + metadata + .ai_block_handle + .as_ref(ctx) + .query_sent_at(ctx) + .is_some() + }) + }); + if has_query_timestamp { + items.push( + MenuItemFields::new("Copy timestamp") + .with_on_select_action(TerminalAction::ContextMenu( + ContextMenuAction::CopyAIBlockTimestamp { ai_block_view_id }, + )) + .into_item(), + ); + } items.push(MenuItem::Separator); items.push( MenuItemFields::new("Save as prompt") diff --git a/app/src/terminal/view_tests.rs b/app/src/terminal/view_tests.rs index 0ea4e78a..322ea22f 100644 --- a/app/src/terminal/view_tests.rs +++ b/app/src/terminal/view_tests.rs @@ -51,6 +51,7 @@ use crate::server::ids::{ClientId, SyncId}; use crate::server::server_api::ai::SpawnAgentRequest; use crate::settings::import::model::ImportedConfigModel; use crate::settings::{AISettings, AppEditorSettings, WarpPromptSeparator}; +use crate::tab::NewSessionMenuItem; use crate::terminal::alt_screen::should_intercept_mouse; use crate::terminal::block_list_element::{SnackbarPoint, SnackbarTranslationMode}; use crate::terminal::block_list_viewport::{ClampingMode, ScrollLines}; @@ -84,7 +85,8 @@ use crate::test_util::terminal::{ }; use crate::test_util::{add_window_with_terminal, assert_eventually}; use crate::view_components::find::FindWithinBlockState; -use crate::workspace::ToastStack; +use crate::workspace::view::tests::{initialize_app as initialize_workspace_app, mock_workspace}; +use crate::workspace::{ToastStack, WorkspaceAction}; use crate::ActiveAgentViewsModel; fn add_window_with_cloud_mode_terminal(app: &mut App) -> ViewHandle { @@ -8427,3 +8429,73 @@ fn hidden_tool_control_changes_preserve_pinned_and_unpinned_scroll() { }); }); } + +#[test] +fn visible_bootstrap_block_leaves_focus_on_tab_rename_editor() { + App::test((), |mut app| async move { + initialize_workspace_app(&mut app); + let workspace = mock_workspace(&mut app); + let (window, terminal) = workspace.update(&mut app, |workspace, ctx| { + workspace.rename_tab(0, ctx); + let terminal = workspace + .active_tab_pane_group() + .as_ref(ctx) + .active_session_view(ctx) + .expect("tab should contain a terminal"); + (ctx.window_id(), terminal) + }); + assert!(workspace.read(&app, |workspace, ctx| { + workspace.is_inline_rename_editor_focused(ctx) + })); + let focused_before = app.focused_view_id(window); + + terminal.update(&mut app, |view, ctx| { + view.handle_terminal_event(&ModelEvent::VisibleBootstrapBlock, ctx); + }); + + assert_eq!(app.focused_view_id(window), focused_before); + assert!(workspace.read(&app, |workspace, ctx| { + workspace.is_inline_rename_editor_focused(ctx) + })); + }); +} + +#[test] +fn visible_bootstrap_block_leaves_focus_on_tab_group_rename_editor() { + let _grouped_tabs_guard = FeatureFlag::GroupedTabs.override_enabled(true); + App::test((), |mut app| async move { + initialize_workspace_app(&mut app); + let workspace = mock_workspace(&mut app); + let (window, terminal, group_id) = workspace.update(&mut app, |workspace, ctx| { + workspace.handle_action( + &WorkspaceAction::SelectNewSessionMenuItem(NewSessionMenuItem::CreateNewTabGroup), + ctx, + ); + let group_id = workspace.tabs[0] + .group_id + .expect("active tab should be assigned to the new group"); + let terminal = workspace + .active_tab_pane_group() + .as_ref(ctx) + .active_session_view(ctx) + .expect("new tab group should contain a terminal"); + (ctx.window_id(), terminal, group_id) + }); + workspace.update(&mut app, |workspace, ctx| { + workspace.rename_tab_group(group_id, ctx); + }); + assert!(workspace.read(&app, |workspace, ctx| { + workspace.is_inline_rename_editor_focused(ctx) + })); + let focused_before = app.focused_view_id(window); + + terminal.update(&mut app, |view, ctx| { + view.handle_terminal_event(&ModelEvent::VisibleBootstrapBlock, ctx); + }); + + assert_eq!(app.focused_view_id(window), focused_before); + assert!(workspace.read(&app, |workspace, ctx| { + workspace.is_inline_rename_editor_focused(ctx) + })); + }); +} diff --git a/app/src/ui_components/icon_with_status.rs b/app/src/ui_components/icon_with_status.rs index 154f43f5..6ee8f48b 100644 --- a/app/src/ui_components/icon_with_status.rs +++ b/app/src/ui_components/icon_with_status.rs @@ -76,6 +76,8 @@ fn circle_padding(total: f32) -> f32 { (circle_size(total) - icon_size(total)) / 2. } +/// Returns the diameter of the status badge's cutout ring, i.e. the badge's +/// full painted footprint. fn badge_size(total: f32, style: StatusBadgeStyle) -> f32 { total * style.ring_ratio } @@ -141,8 +143,11 @@ pub(crate) enum IconWithStatusVariant { is_ambient: bool, }, /// A pre-rendered avatar with an optional status overlay (cloud lobe when - /// ambient). Caller must size `avatar` to `circle_size(total_size)` so the - /// overlay's overhang matches the other variants. + /// ambient). The overlay is anchored to the `total_size` box's bottom-right + /// corner however big `avatar` is, so pass either an avatar sized to + /// `circle_size(total_size)` (to match the overhang of the other variants) + /// or an element that already fills the `total_size` box and places its own + /// artwork inside it. CustomAvatar { avatar: Box, status: Option, @@ -444,9 +449,8 @@ fn render_with_optional_status_badge( status_container_background: GalaxyThemeFill, ) -> Box { let Some(status) = status else { - // No status badge: still occupy the full `total_size` footprint so the agent - // circle (which is only `circle_size(total)` wide) sits centered in the box - // the caller reserved. + // Reserve the same footprint with or without a badge. ConstrainedBox does not + // center the circle; callers that need centering must wrap it in Align. return ConstrainedBox::new(circle) .with_width(total_size) .with_height(total_size) diff --git a/app/src/util/bindings.rs b/app/src/util/bindings.rs index 69a424cc..3f467fc4 100644 --- a/app/src/util/bindings.rs +++ b/app/src/util/bindings.rs @@ -383,7 +383,6 @@ pub fn custom_tag_to_keystroke(custom: CustomTag) -> Option { } } - // This is one of the app's hardcoded keybindings. CustomAction::AddWindow => Keystroke::parse(cmd_or_ctrl_shift("n")).ok(), CustomAction::ToggleGalaxyDrive => { if OperatingSystem::get().is_mac() { diff --git a/app/src/util/time_format.rs b/app/src/util/time_format.rs index 6e6d14aa..04ee281c 100644 --- a/app/src/util/time_format.rs +++ b/app/src/util/time_format.rs @@ -2,6 +2,14 @@ use std::ops::Sub; use chrono::{DateTime, Duration, Local, Utc}; +pub fn format_message_timestamp(datetime: &DateTime) -> String { + datetime.format("%-m/%-d at %-I:%M %p").to_string() +} + +pub fn is_trustworthy_message_timestamp(datetime: &DateTime) -> bool { + datetime != &DateTime::::default() +} + // Some conversion ratios for time units. const SEC_TO_MS: f64 = 1000.; const MIN_TO_MS: f64 = 60. * SEC_TO_MS; diff --git a/app/src/util/time_format_tests.rs b/app/src/util/time_format_tests.rs index e1beb8ec..30304174 100644 --- a/app/src/util/time_format_tests.rs +++ b/app/src/util/time_format_tests.rs @@ -1,5 +1,26 @@ +use chrono::TimeZone; + use super::*; +#[test] +fn format_message_timestamp_uses_friendly_local_time() { + let morning = Local.with_ymd_and_hms(2026, 8, 10, 9, 2, 0).unwrap(); + let noon = Local.with_ymd_and_hms(2026, 12, 31, 12, 45, 0).unwrap(); + let midnight = Local.with_ymd_and_hms(2026, 12, 31, 0, 5, 0).unwrap(); + + assert_eq!(format_message_timestamp(&morning), "8/10 at 9:02 AM"); + assert_eq!(format_message_timestamp(&noon), "12/31 at 12:45 PM"); + assert_eq!(format_message_timestamp(&midnight), "12/31 at 12:05 AM"); +} + +#[test] +fn default_message_timestamp_is_not_trustworthy() { + assert!(!is_trustworthy_message_timestamp(&DateTime::default())); + assert!(is_trustworthy_message_timestamp( + &Local.with_ymd_and_hms(2026, 8, 10, 9, 22, 0).unwrap() + )); +} + #[test] fn test_format_sigfigs() { assert_eq!(format_sigfigs(0.000456, 2,), "0.00046"); diff --git a/app/src/workspace/mod.rs b/app/src/workspace/mod.rs index 1623a745..e52983c2 100644 --- a/app/src/workspace/mod.rs +++ b/app/src/workspace/mod.rs @@ -69,11 +69,11 @@ use crate::workspace::view::{ LEFT_PANEL_AGENT_CONVERSATIONS_BINDING_NAME, LEFT_PANEL_GLOBAL_SEARCH_BINDING_NAME, LEFT_PANEL_PROJECT_EXPLORER_BINDING_NAME, LEFT_PANEL_WARP_DRIVE_BINDING_NAME, NEW_AGENT_TAB_BINDING_NAME, NEW_AMBIENT_AGENT_TAB_BINDING_NAME, NEW_FILE_BINDING_NAME, - NEW_TAB_BINDING_NAME, NEW_TERMINAL_TAB_BINDING_NAME, OPEN_GLOBAL_SEARCH_BINDING_NAME, - TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME, TOGGLE_NOTIFICATION_MAILBOX_BINDING_NAME, - TOGGLE_PROJECT_EXPLORER_BINDING_NAME, TOGGLE_RIGHT_PANEL_BINDING_NAME, - TOGGLE_TAB_CONFIGS_MENU_BINDING_NAME, TOGGLE_VERTICAL_TABS_PANEL_BINDING_NAME, - TOGGLE_WARP_DRIVE_BINDING_NAME, + NEW_TAB_BINDING_NAME, NEW_TERMINAL_TAB_BINDING_NAME, NEW_WINDOW_BINDING_NAME, + OPEN_GLOBAL_SEARCH_BINDING_NAME, TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME, + TOGGLE_NOTIFICATION_MAILBOX_BINDING_NAME, TOGGLE_PROJECT_EXPLORER_BINDING_NAME, + TOGGLE_RIGHT_PANEL_BINDING_NAME, TOGGLE_TAB_CONFIGS_MENU_BINDING_NAME, + TOGGLE_VERTICAL_TABS_PANEL_BINDING_NAME, TOGGLE_WARP_DRIVE_BINDING_NAME, }; pub fn init(app: &mut AppContext) { @@ -307,15 +307,17 @@ pub fn init(app: &mut AppContext) { "Switch to previous tab", id!("Workspace") & id!("Workspace_MultipleTabs"), ), - FixedBinding::custom( - CustomAction::AddWindow, - WorkspaceAction::AddWindow, - "Create New Window", - id!("Workspace"), - ) - .with_enabled(|| ContextFlag::CreateNewSession.is_enabled()), ]); + app.register_editable_bindings([EditableBinding::new( + NEW_WINDOW_BINDING_NAME, + BindingDescription::new("Create New Window"), + WorkspaceAction::AddWindow, + ) + .with_custom_action(CustomAction::AddWindow) + .with_context_predicate(id!("Workspace")) + .with_enabled(|| ContextFlag::CreateNewSession.is_enabled())]); + app.register_editable_bindings([EditableBinding::new( NEW_FILE_BINDING_NAME, BindingDescription::new("New File"), diff --git a/app/src/workspace/view.rs b/app/src/workspace/view.rs index 3ee097eb..57d82eb9 100644 --- a/app/src/workspace/view.rs +++ b/app/src/workspace/view.rs @@ -98,6 +98,7 @@ use galaxyui::{ AppContext, Entity, EntityId, FocusContext, ModelHandle, SingletonEntity, TypedActionView, UpdateModel, UpdateView, View, ViewAsRef, ViewContext, ViewHandle, WeakViewHandle, WindowId, }; +use instant::Instant; use itertools::Itertools; use lazy_static::lazy_static; pub(crate) use onboarding::OnboardingTutorial; @@ -630,6 +631,7 @@ pub(crate) const TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME: &str = pub(crate) const NEW_TAB_BINDING_NAME: &str = "workspace:new_tab"; pub(crate) const NEW_TERMINAL_TAB_BINDING_NAME: &str = "workspace:new_terminal_tab"; pub(crate) const NEW_FILE_BINDING_NAME: &str = "workspace:new_file"; +pub(crate) const NEW_WINDOW_BINDING_NAME: &str = "workspace:new_window"; pub(crate) const NEW_AGENT_TAB_BINDING_NAME: &str = "workspace:new_agent_tab"; pub(crate) const NEW_AMBIENT_AGENT_TAB_BINDING_NAME: &str = "workspace:new_ambient_agent_tab"; pub(crate) const TOGGLE_TAB_CONFIGS_MENU_BINDING_NAME: &str = "workspace:toggle_tab_configs_menu"; @@ -1034,6 +1036,7 @@ pub struct Workspace { import_modal: ViewHandle, theme_chooser_view: ViewHandle, previous_theme: Option, + background_image_animation_start_time: Instant, reward_modal: ViewHandle>, reward_modal_pending: Option, pub(crate) current_workspace_state: WorkspaceState, @@ -1462,10 +1465,15 @@ impl Workspace { .is_any_tab_group_being_renamed() { match event { - EditorEvent::Blurred | EditorEvent::Enter => { + EditorEvent::Enter => { self.finish_tab_group_rename(ctx); } - EditorEvent::Escape => { + // Blur discards rather than commits. Focus can leave this editor without + // the user ever ending the rename — #14241 is one such case — and + // committing then writes a half-typed fragment as the group's real, + // persisted name. Discarding loses nothing the user cannot retype, and + // Enter remains the way to confirm. + EditorEvent::Blurred | EditorEvent::Escape => { self.cancel_tab_group_rename(ctx); } _ => {} @@ -3354,6 +3362,7 @@ impl Workspace { ctrl_tab_palette, mouse_states: Default::default(), previous_theme: None, + background_image_animation_start_time: Instant::now(), settings_pane, theme_chooser_view, reward_modal, @@ -7131,6 +7140,18 @@ impl Workspace { } } + pub(crate) fn is_inline_rename_editor_focused(&self, ctx: &AppContext) -> bool { + match ctx.focused_view_id(self.window_id) { + Some(id) if id == self.tab_rename_editor.id() => { + self.current_workspace_state.is_tab_being_renamed() + } + Some(id) if id == self.tab_group_rename_editor.id() => self + .current_workspace_state + .is_any_tab_group_being_renamed(), + _ => false, + } + } + /// Opens the inline rename editor over the given group's header. pub fn rename_tab_group(&mut self, group_id: TabGroupId, ctx: &mut ViewContext) { let Some(group) = self.tab_groups.get(&group_id) else { @@ -27101,6 +27122,9 @@ impl View for Workspace { .cover() .with_opacity(opacity_ratio) .with_corner_radius(window_corner_radius) + .enable_animation_with_start_time( + self.background_image_animation_start_time, + ) .finish(), ) .finish(), diff --git a/app/src/workspace/view_tests.rs b/app/src/workspace/view_tests.rs index f7df4e22..d43f82e6 100644 --- a/app/src/workspace/view_tests.rs +++ b/app/src/workspace/view_tests.rs @@ -28,6 +28,8 @@ use crate::ai::agent_tips::AITipModel; use crate::ai::ambient_agents::github_auth_notifier::GitHubAuthNotifier; use crate::ai::blocklist::agent_view::orchestration_pill_bar_model::OrchestrationPillBarModel; use crate::ai::blocklist::{BlocklistAIHistoryModel, BlocklistAIPermissions}; +#[cfg(not(target_family = "wasm"))] +use crate::ai::chatgpt_auth::ChatGPTAuthModel; use crate::ai::document::ai_document_model::AIDocumentModel; use crate::ai::execution_profiles::profiles::AIExecutionProfilesModel; use crate::ai::facts::manager::AIFactManager; @@ -178,6 +180,8 @@ pub(crate) fn initialize_app(app: &mut App) { app.add_singleton_model(AgentNotificationsModel::new); app.add_singleton_model(AgentConversationsModel::new); app.add_singleton_model(SessionPermissionsManager::new); + #[cfg(not(target_family = "wasm"))] + app.add_singleton_model(|_| ChatGPTAuthModel::new_for_test()); app.add_singleton_model(LLMPreferences::new); app.add_singleton_model(HarnessAvailabilityModel::new); app.add_singleton_model(|ctx| AITipModel::new_for_agent_tips(ctx)); @@ -255,8 +259,9 @@ pub(crate) fn initialize_app(app: &mut App) { app.add_singleton_model(|ctx| { CodebaseIndexManager::new_for_test(ServerApiProvider::as_ref(ctx).get(), ctx) }); - app.add_singleton_model(|ctx| PersistedWorkspace::new(vec![], HashMap::new(), None, ctx)); + // PersistedWorkspace subscribes to project-context updates during construction. app.add_singleton_model(|_| ProjectContextModel::default()); + app.add_singleton_model(|ctx| PersistedWorkspace::new(vec![], HashMap::new(), None, ctx)); app.add_singleton_model(|_| PricingInfoModel::new()); app.add_singleton_model(AIDocumentModel::new); app.add_singleton_model(|_| History::new(vec![])); @@ -4257,3 +4262,52 @@ fn test_pin_tab_on_grouped_tab_extracts_then_pins() { }); }); } + +/// Regression test for #14241. +/// +/// Creating a tab group opens the inline name editor and also spawns a terminal. About +/// a second later that terminal's bootstrap block becomes visible and takes focus, which +/// blurs the editor while the user is still typing. Blur used to be treated as +/// confirmation, so whatever fragment had been typed became the group's name — and was +/// persisted. +/// +/// A rename the user never finished must not be committed. +#[test] +fn test_tab_group_rename_blur_does_not_commit_unfinished_name() { + let _grouped_tabs_guard = FeatureFlag::GroupedTabs.override_enabled(true); + + App::test((), |mut app| async move { + initialize_app(&mut app); + + let workspace = mock_workspace(&mut app); + workspace.update(&mut app, |workspace, ctx| { + workspace.handle_action( + &WorkspaceAction::SelectNewSessionMenuItem(NewSessionMenuItem::CreateNewTabGroup), + ctx, + ); + let group_id = workspace.tabs[0] + .group_id + .expect("active tab should be assigned to the new group"); + assert_eq!(workspace.tab_groups[&group_id].name, None); + + workspace.rename_tab_group(group_id, ctx); + + // The user gets three characters in before the terminal is ready. + workspace + .tab_group_rename_editor + .update(ctx, |editor, ctx| { + editor.clear_buffer_and_reset_undo_stack(ctx); + editor.user_insert("Bui", ctx); + }); + + // The auto-created terminal takes focus; the editor blurs with no user intent + // to finish. + workspace.handle_tab_group_rename_editor_event(&EditorEvent::Blurred, ctx); + + assert_eq!( + workspace.tab_groups[&group_id].name, None, + "a rename interrupted by the terminal stealing focus must not be committed" + ); + }); + }); +} diff --git a/crates/galaxyui_core/src/elements/event_handler.rs b/crates/galaxyui_core/src/elements/event_handler.rs index a1ec5d52..fe9db383 100644 --- a/crates/galaxyui_core/src/elements/event_handler.rs +++ b/crates/galaxyui_core/src/elements/event_handler.rs @@ -1,14 +1,18 @@ use std::cell::RefCell; +use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::Vector2F; use super::{ AfterLayoutContext, AppContext, DispatchEventResult, Element, Event, EventContext, - LayoutContext, PaintContext, Point, SizeConstraint, ZIndex, + LayoutContext, PaintContext, Point, SelectableElement, Selection, SelectionFragment, + SizeConstraint, ZIndex, }; use crate::event::{DispatchedEvent, EventDiscriminants, KeyState, ModifiersState}; use crate::keymap::Keystroke; use crate::platform::keyboard::KeyCode; +use crate::text::word_boundaries::WordBoundariesPolicy; +use crate::text::{IsRect, SelectionDirection, SelectionType}; type Handler = Box DispatchEventResult>; type KeyHandler = Box DispatchEventResult>; @@ -361,11 +365,75 @@ impl Element for EventHandler { handled } + fn as_selectable_element(&self) -> Option<&dyn SelectableElement> { + Some(self) + } + fn origin(&self) -> Option { self.child.origin() } } +impl SelectableElement for EventHandler { + fn get_selection( + &self, + selection_start: Vector2F, + selection_end: Vector2F, + is_rect: IsRect, + ) -> Option> { + self.child + .as_selectable_element() + .and_then(|selectable_child| { + selectable_child.get_selection(selection_start, selection_end, is_rect) + }) + } + + fn expand_selection( + &self, + point: Vector2F, + direction: SelectionDirection, + unit: SelectionType, + word_boundaries_policy: &WordBoundariesPolicy, + ) -> Option { + self.child + .as_selectable_element() + .and_then(|selectable_child| { + selectable_child.expand_selection(point, direction, unit, word_boundaries_policy) + }) + } + + fn is_point_semantically_before( + &self, + absolute_point: Vector2F, + absolute_point_other: Vector2F, + ) -> Option { + self.child + .as_selectable_element() + .and_then(|selectable_child| { + selectable_child.is_point_semantically_before(absolute_point, absolute_point_other) + }) + } + + fn smart_select( + &self, + absolute_point: Vector2F, + smart_select_fn: crate::elements::SmartSelectFn, + ) -> Option<(Vector2F, Vector2F)> { + self.child + .as_selectable_element() + .and_then(|selectable_child| { + selectable_child.smart_select(absolute_point, smart_select_fn) + }) + } + + fn calculate_clickable_bounds(&self, current_selection: Option) -> Vec { + self.child + .as_selectable_element() + .map(|selectable_child| selectable_child.calculate_clickable_bounds(current_selection)) + .unwrap_or_default() + } +} + #[cfg(test)] #[path = "event_handler_test.rs"] mod tests; diff --git a/crates/galaxyui_core/src/elements/formatted_text_element.rs b/crates/galaxyui_core/src/elements/formatted_text_element.rs index 3c30c259..4be61768 100644 --- a/crates/galaxyui_core/src/elements/formatted_text_element.rs +++ b/crates/galaxyui_core/src/elements/formatted_text_element.rs @@ -2556,6 +2556,10 @@ impl SelectableElement for FormattedTextElement { let line = text_frame.lines().get(text_selection_bound.row_index)?; let first_glyph = line.first_glyph()?; let last_glyph = line.last_glyph()?; + // Visual glyph order can oppose logical indices, and usize::clamp panics if min > max. + if first_glyph.index > last_glyph.index { + return None; + } // If we clicked to the right of a line, the text_selection_bound's glyph index would be one larger than the last glyph's index. // Snap it within the line's index range so we do smart selection as if we clicked on the last glyph in the line. let char_offset = text_selection_bound diff --git a/crates/galaxyui_core/src/elements/formatted_text_element_tests.rs b/crates/galaxyui_core/src/elements/formatted_text_element_tests.rs index eea1bd68..a79f018e 100644 --- a/crates/galaxyui_core/src/elements/formatted_text_element_tests.rs +++ b/crates/galaxyui_core/src/elements/formatted_text_element_tests.rs @@ -9,6 +9,7 @@ use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::vec2f; use string_offset::ByteOffset; +use vec1::vec1; use super::{ apply_secret_replacements, FormattedTextElement, FrameMouseHandlers, @@ -16,9 +17,9 @@ use super::{ SecretRange, }; use crate::elements::{Point, SelectableElement, ZIndex}; -use crate::fonts::FamilyId; +use crate::fonts::{FamilyId, FontId}; use crate::text::BlockHeaderSize; -use crate::text_layout::TextFrame; +use crate::text_layout::{Glyph, Line, Run, TextAlignment, TextFrame, TextStyle}; #[test] fn test_default_heading_font_size_multipliers() { @@ -220,3 +221,78 @@ fn smart_select_returns_none_when_point_is_outside_horizontal_bounds() { .smart_select(point, select_first_character) .is_none()); } + +const TEST_GLYPH_ADVANCE: f32 = 10.0; + +/// Builds a single-line element whose visual first glyph has a higher logical index than +/// its visual last glyph, which RTL layout can produce. +fn text_element_with_reversed_endpoint_glyph_indices(text: &str) -> FormattedTextElement { + let char_count = text.chars().count(); + let width = char_count as f32 * TEST_GLYPH_ADVANCE; + let glyphs = text + .chars() + .enumerate() + .map(|(visual_index, _)| Glyph { + id: Default::default(), + position_along_baseline: vec2f(visual_index as f32 * TEST_GLYPH_ADVANCE, 0.0), + index: char_count - 1 - visual_index, + width: TEST_GLYPH_ADVANCE, + }) + .collect(); + let mut line = Line::mock(vec![Run { + font_id: FontId(0), + glyphs, + styles: TextStyle::new(), + width, + }]); + line.width = width; + let text_frame = Arc::new(TextFrame::new(vec1![line], width, TextAlignment::Left)); + let frame_bounds = RectF::new(vec2f(0.0, 0.0), vec2f(width, text_frame.height())); + let mouse_handlers = Rc::new(RefCell::new(FrameMouseHandlers::default())); + let formatted_text = FormattedText::new([FormattedTextLine::Line(vec![ + FormattedTextFragment::plain_text(text), + ])]); + + let mut element = FormattedTextElement::new( + formatted_text, + 13.0, + FamilyId(0), + FamilyId(0), + ColorU::black(), + HighlightedHyperlink::default(), + ); + element.origin = Some(Point::new(0.0, 0.0, ZIndex::new(0))); + element.size = Some(frame_bounds.size()); + element.laid_out_text = vec![LaidOutTextFrame::Text { + text_frame, + frame_bounds, + bottom_padding: 0.0, + raw_text: text.to_string(), + mouse_handlers: mouse_handlers.clone(), + }]; + element.text_frame_mouse_handlers = vec![mouse_handlers]; + element.hyperlink_support = HyperlinkSupport { + highlighted_hyperlink: Arc::new(Mutex::new(None)), + hyperlink_font_color: ColorU::black(), + }; + element +} + +#[test] +fn smart_select_returns_none_when_line_glyph_indices_are_reversed() { + let element = text_element_with_reversed_endpoint_glyph_indices("abc"); + let line = match element.laid_out_text.first() { + Some(LaidOutTextFrame::Text { text_frame, .. }) => { + text_frame.lines().first().expect("line") + } + _ => panic!("expected a text frame"), + }; + assert!( + line.first_glyph().expect("first glyph").index + > line.last_glyph().expect("last glyph").index + ); + + assert!(element + .smart_select(vec2f(5.0, 5.0), select_first_character) + .is_none()); +} diff --git a/crates/galaxyui_core/src/elements/selectable_area.rs b/crates/galaxyui_core/src/elements/selectable_area.rs index 7cb6d3dd..4e7691e5 100644 --- a/crates/galaxyui_core/src/elements/selectable_area.rs +++ b/crates/galaxyui_core/src/elements/selectable_area.rs @@ -14,7 +14,7 @@ use string_offset::ByteOffset; use super::{ AfterLayoutContext, AppContext, ColorU, Element, Event, EventContext, LayoutContext, - PaintContext, Point, SelectionFragment, SizeConstraint, + PaintContext, Point, SelectionFragment, SizeConstraint, ZIndex, }; use crate::event::{DispatchedEvent, ModifiersState}; use crate::text::word_boundaries::WordBoundariesPolicy; @@ -42,6 +42,11 @@ pub struct SelectableArea { smart_select_fn: Option, should_support_rect_select: bool, + + // Upper bound of z-indexes painted by the child subtree. Mouse-down hit testing uses this + // instead of the origin's z-index so child content that starts its own layers (e.g. a clipped + // scrollable) isn't treated as covering this element; later-painted overlays still are. + child_max_z_index: Option, } /// Stores the selection start and end points. We include the option to store @@ -233,6 +238,7 @@ impl SelectableArea { word_boundaries_policy: WordBoundariesPolicy::Default, smart_select_fn: None, should_support_rect_select: false, + child_max_z_index: None, } } @@ -650,6 +656,7 @@ impl Element for SelectableArea { ctx.current_selection = self.get_current_selection_absolute(); self.child.paint(origin, ctx, app); ctx.current_selection = None; + self.child_max_z_index = Some(ctx.scene.max_active_z_index()); } fn dispatch_event( @@ -675,7 +682,8 @@ impl Element for SelectableArea { event.raw_event(), Event::LeftMouseDown { .. } | Event::RightMouseDown { .. } ) && self - .z_index() + .child_max_z_index + .or_else(|| self.z_index()) .is_some_and(|z_index| event.at_z_index(z_index, ctx).is_none()) { return false; @@ -755,3 +763,7 @@ impl Element for SelectableArea { self.origin } } + +#[cfg(test)] +#[path = "selectable_area_tests.rs"] +mod tests; diff --git a/crates/galaxyui_core/src/elements/selectable_area_tests.rs b/crates/galaxyui_core/src/elements/selectable_area_tests.rs new file mode 100644 index 00000000..49453081 --- /dev/null +++ b/crates/galaxyui_core/src/elements/selectable_area_tests.rs @@ -0,0 +1,221 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use pathfinder_geometry::rect::RectF; + +use super::*; +use crate::elements::new_scrollable::SingleAxisConfig; +use crate::elements::{ + ClippedScrollStateHandle, EventHandler, Fill, NewScrollable, SelectableElement, +}; +use crate::platform::WindowStyle; +use crate::{App, Entity, EntityIdSet, Presenter, TypedActionView, WindowInvalidation}; + +/// Selectable element that paints a hit-recorded rect (as `Container` does), so content inside a +/// layer-starting parent registers as covered at lower z-indexes. +#[derive(Default)] +struct LayeredSelectableProbe { + origin: Option, +} + +const PROBE_SIZE: f32 = 100.; + +impl Element for LayeredSelectableProbe { + fn layout( + &mut self, + _constraint: SizeConstraint, + _ctx: &mut LayoutContext, + _app: &AppContext, + ) -> Vector2F { + vec2f(PROBE_SIZE, PROBE_SIZE) + } + + fn after_layout(&mut self, _ctx: &mut AfterLayoutContext, _app: &AppContext) {} + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _app: &AppContext) { + self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index())); + ctx.scene + .draw_rect_with_hit_recording(RectF::new(origin, vec2f(PROBE_SIZE, PROBE_SIZE))) + .with_background(Fill::None); + } + + fn size(&self) -> Option { + Some(vec2f(PROBE_SIZE, PROBE_SIZE)) + } + + fn origin(&self) -> Option { + self.origin + } + + fn dispatch_event( + &mut self, + _event: &DispatchedEvent, + _ctx: &mut EventContext, + _app: &AppContext, + ) -> bool { + false + } + + fn as_selectable_element(&self) -> Option<&dyn SelectableElement> { + Some(self) + } +} + +impl SelectableElement for LayeredSelectableProbe { + fn get_selection( + &self, + _selection_start: Vector2F, + _selection_end: Vector2F, + _is_rect: IsRect, + ) -> Option> { + Some(vec![SelectionFragment { + text: "probe".to_string(), + origin: self.origin?, + }]) + } + + fn expand_selection( + &self, + _absolute_point: Vector2F, + _direction: SelectionDirection, + _unit: SelectionType, + _word_boundaries_policy: &WordBoundariesPolicy, + ) -> Option { + None + } + + fn is_point_semantically_before( + &self, + absolute_point: Vector2F, + absolute_point_other: Vector2F, + ) -> Option { + Some( + (absolute_point.y(), absolute_point.x()) + < (absolute_point_other.y(), absolute_point_other.x()), + ) + } + + fn smart_select( + &self, + _absolute_point: Vector2F, + _smart_select_fn: crate::elements::SmartSelectFn, + ) -> Option<(Vector2F, Vector2F)> { + None + } + + fn calculate_clickable_bounds(&self, _current_selection: Option) -> Vec { + Vec::new() + } +} + +#[derive(Default)] +struct ScrollableSelectionView { + selection_handle: SelectionHandle, + captured_selection: Rc>>>, +} + +impl Entity for ScrollableSelectionView { + type Event = (); +} + +impl crate::core::View for ScrollableSelectionView { + fn ui_name() -> &'static str { + "selectable_area_test_view" + } + + fn render(&self, _: &AppContext) -> Box { + let captured_selection = self.captured_selection.clone(); + // Mirrors the element sandwich around collapsible reasoning block bodies: an + // EventHandler wrapping a clipped scrollable. + SelectableArea::new( + self.selection_handle.clone(), + move |args, _, _| { + *captured_selection.borrow_mut() = Some(args.selection); + }, + EventHandler::new( + NewScrollable::vertical( + SingleAxisConfig::Clipped { + handle: ClippedScrollStateHandle::default(), + child: Box::new(LayeredSelectableProbe::default()), + }, + Fill::None, + Fill::None, + Fill::None, + ) + .finish(), + ) + .finish(), + ) + .finish() + } +} + +impl TypedActionView for ScrollableSelectionView { + type Action = (); +} + +/// A drag starting on content painted in a child's own layers must start a selection and yield +/// the child's fragments on mouse up. Covers the mouse-down hit test at the child subtree's max +/// z-index and `EventHandler`'s forwarding of selection queries. +#[test] +fn selection_starts_on_content_painted_in_child_layers() { + App::test((), |mut app| async move { + let (window_id, view) = app.add_window(WindowStyle::NotStealFocus, |_| { + ScrollableSelectionView::default() + }); + let (selection_handle, captured_selection) = view.read(&app, |view, _| { + ( + view.selection_handle.clone(), + view.captured_selection.clone(), + ) + }); + + let root_view_id = view.id(); + app.update(move |ctx| { + let mut presenter = Presenter::new(window_id); + let mut updated = EntityIdSet::default(); + updated.insert(root_view_id); + let invalidation = WindowInvalidation { + updated, + ..Default::default() + }; + presenter.invalidate(invalidation, ctx); + presenter.build_scene(vec2f(200., 200.), 1., None, ctx); + let presenter = Rc::new(RefCell::new(presenter)); + + ctx.simulate_window_event( + Event::LeftMouseDown { + position: vec2f(10., 10.), + modifiers: Default::default(), + click_count: 1, + is_first_mouse: false, + }, + window_id, + presenter.clone(), + ); + ctx.simulate_window_event( + Event::LeftMouseDragged { + position: vec2f(60., 10.), + modifiers: Default::default(), + }, + window_id, + presenter.clone(), + ); + ctx.simulate_window_event( + Event::LeftMouseUp { + position: vec2f(60., 10.), + modifiers: Default::default(), + }, + window_id, + presenter, + ); + }); + + assert_eq!( + *captured_selection.borrow(), + Some(Some("probe".to_string())), + "the drag should have produced a selection from the layered child", + ); + assert!(!selection_handle.is_selecting()); + }); +} diff --git a/crates/galaxyui_core/src/fonts.rs b/crates/galaxyui_core/src/fonts.rs index cad041b8..ff2091bb 100644 --- a/crates/galaxyui_core/src/fonts.rs +++ b/crates/galaxyui_core/src/fonts.rs @@ -509,12 +509,21 @@ impl Cache { let (glyph_id, _) = self .glyph_for_char(font_id, 'm', false) .expect("we verify in Config::new that the font has an 'm' glyph"); - let bounds = self - .glyph_typographic_bounds(font_id, font_size, glyph_id) - .expect( - "we verify in Config::new that we can measure the typographic bounds of the 'm' glyph", - ); - bounds.width() + // Some Windows fonts map 'm' with a horizontal advance but no outline bounding box. + match self.glyph_typographic_bounds(font_id, font_size, glyph_id) { + Ok(bounds) => bounds.width(), + Err(_) => match self.glyph_advance(font_id, font_size, glyph_id) { + Ok(advance) if advance.x().is_finite() && advance.x() > 0.0 => advance.x(), + advance => { + log::warn!( + "[em_width] 'm' glyph has no usable horizontal advance; falling back to \ + font_size font_id={font_id:?} font_size={font_size} advance={advance:?}" + ); + // Scroll converts pixels with `/ em_width`; keep a positive width. + font_size.max(1.0) + } + }, + } } pub(crate) fn remove_glyphs_by_char_entry(&mut self, key: (FontId, char)) { diff --git a/crates/galaxyui_core/src/fonts_tests.rs b/crates/galaxyui_core/src/fonts_tests.rs index 51f52a6b..c6d05992 100644 --- a/crates/galaxyui_core/src/fonts_tests.rs +++ b/crates/galaxyui_core/src/fonts_tests.rs @@ -1,4 +1,9 @@ +use anyhow::{anyhow, Result}; +use pathfinder_geometry::rect::RectI; +use pathfinder_geometry::vector::{vec2i, Vector2F, Vector2I}; + use super::*; +use crate::platform::{self, TextLayoutSystem}; #[test] fn test_subpixel_alignment_computation() { @@ -40,3 +45,137 @@ fn test_subpixel_alignment_computation() { assert_eq!(alignment.0, 0); } } + +struct EmWidthFontDB { + bounds: Option, + advance: Option, +} + +impl EmWidthFontDB { + fn cache(bounds: Option, advance: Option) -> Cache { + Cache::new(Box::new(Self { bounds, advance })) + } +} + +impl platform::FontDB for EmWidthFontDB { + fn load_from_bytes(&mut self, _name: &str, _bytes: Vec>) -> Result { + unimplemented!() + } + + #[cfg(not(target_family = "wasm"))] + fn load_from_system(&mut self, _font_family: &str) -> Result { + unimplemented!() + } + + #[cfg(not(target_family = "wasm"))] + fn load_all_system_fonts( + &self, + ) -> futures::future::BoxFuture<'static, Box> { + unimplemented!() + } + + #[cfg(not(target_family = "wasm"))] + fn process_loaded_system_fonts( + &mut self, + _loaded_system_fonts: Box, + ) -> Vec<(Option, FontInfo)> { + unimplemented!() + } + + fn fallback_fonts(&self, _ch: char, _font_id: FontId) -> Vec { + unimplemented!() + } + + fn select_font(&self, _family_id: FamilyId, _properties: Properties) -> FontId { + FontId(0) + } + + fn font_metrics(&self, _font_id: FontId) -> Metrics { + Metrics { + units_per_em: 16, + ascent: 12, + descent: -4, + line_gap: 0, + } + } + + fn glyph_advance(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result { + self.advance.ok_or_else(|| anyhow!("No advance for glyph")) + } + + fn load_family_name_from_id(&self, _id: FamilyId) -> Option { + unimplemented!() + } + + fn glyph_raster_bounds( + &self, + _font_id: FontId, + _size: f32, + _glyph_id: GlyphId, + _scale: Vector2F, + _glyph_config: &crate::rendering::GlyphConfig, + ) -> Result { + unimplemented!() + } + + fn glyph_typographic_bounds(&self, _font_id: FontId, glyph_id: GlyphId) -> Result { + self.bounds + .ok_or_else(|| anyhow!("No bounding box for glyph id {glyph_id}")) + } + + fn rasterize_glyph( + &self, + _font_id: FontId, + _size: f32, + _glyph_id: GlyphId, + _scale: Vector2F, + _subpixel_alignment: SubpixelAlignment, + _glyph_config: &crate::rendering::GlyphConfig, + _format: canvas::RasterFormat, + ) -> Result { + unimplemented!() + } + + fn glyph_for_char(&self, _font_id: FontId, _char: char) -> Option { + Some(0) + } + + fn family_id_for_name(&self, _name: &str) -> Option { + unimplemented!() + } + + fn text_layout_system(&self) -> &dyn TextLayoutSystem { + unimplemented!() + } +} + +#[test] +fn em_width_uses_typographic_bounds_width_when_available() { + let cache = EmWidthFontDB::cache( + Some(RectI::new(vec2i(0, 0), vec2i(10, 12))), + Some(vec2i(20, 0)), + ); + + assert_eq!(cache.em_width(FamilyId(0), 16.0), 10.0); +} + +#[test] +fn em_width_uses_horizontal_advance_when_typographic_bounds_are_missing() { + let cache = EmWidthFontDB::cache(None, Some(vec2i(20, 0))); + + assert_eq!(cache.em_width(FamilyId(0), 16.0), 20.0); +} + +#[test] +fn em_width_does_not_panic_when_bounds_and_advance_are_missing() { + let cache = EmWidthFontDB::cache(None, None); + + assert_eq!(cache.em_width(FamilyId(0), 16.0), 16.0); +} + +#[test] +fn em_width_falls_back_when_horizontal_advance_is_zero() { + let cache = EmWidthFontDB::cache(None, Some(vec2i(0, 0))); + + assert_eq!(cache.em_width(FamilyId(0), 16.0), 16.0); +} diff --git a/crates/galaxyui_core/src/image_cache_tests.rs b/crates/galaxyui_core/src/image_cache_tests.rs index a13430dd..536b7a86 100644 --- a/crates/galaxyui_core/src/image_cache_tests.rs +++ b/crates/galaxyui_core/src/image_cache_tests.rs @@ -708,3 +708,245 @@ fn test_respects_max_dimensions_for_cacheoption_bysize() { // Assert that, when we specify a max dimension of 512, the image is resized accordingly. assert_eq!(image.img.dimensions(), (512, 512)); } + +#[test] +fn animated_image_get_current_frame_advances_with_elapsed_time() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + // Load an animated GIF with FullAnimation behavior to get an AnimatedImage + let image = load_bundled_image( + &image_cache, + &asset_cache, + "numbers-1000ms.gif", + Vector2I::new(16, 16), + FitType::Contain, + AnimatedImageBehavior::FullAnimation, + ); + + let Image::Animated(animated) = image.as_ref() else { + panic!("Expected animated image but got static image!"); + }; + + // The numbers-1000ms.gif has multiple frames with various delays + assert!( + animated.frames.len() >= 2, + "Expected at least 2 frames for testing" + ); + assert!(animated.duration > 0, "Expected positive total duration"); + + let (_frame_0, remaining_0) = animated + .get_current_frame(0) + .expect("Should get frame at elapsed 0ms"); + let (_frame_100, remaining_100) = animated + .get_current_frame(100) + .expect("Should get frame at elapsed 100ms"); + let (_frame_500, remaining_500) = animated + .get_current_frame(500) + .expect("Should get frame at elapsed 500ms"); + + assert_ne!( + remaining_0, remaining_100, + "Remaining delay should differ at different elapsed times" + ); + assert_ne!( + remaining_100, remaining_500, + "Remaining delay should differ at different elapsed times" + ); +} + +#[test] +fn animated_image_get_current_frame_wraps_at_duration() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + let image = load_bundled_image( + &image_cache, + &asset_cache, + "numbers-1000ms.gif", + Vector2I::new(16, 16), + FitType::Contain, + AnimatedImageBehavior::FullAnimation, + ); + + let Image::Animated(animated) = image.as_ref() else { + panic!("Expected animated image but got static image!"); + }; + + let duration = animated.duration; + + let (frame_start, _) = animated + .get_current_frame(0) + .expect("Should get frame at start"); + let (frame_end, _) = animated + .get_current_frame(duration - 1) + .expect("Should get frame near end"); + + // After wrapping (elapsed >= duration), should return to start of animation + let (frame_wrapped_start, _) = animated + .get_current_frame(duration) + .expect("Should get frame after one complete cycle"); + let (frame_wrapped_end, _) = animated + .get_current_frame(duration * 2 - 1) + .expect("Should get frame in second cycle"); + + assert!( + Arc::ptr_eq(&frame_start, &frame_wrapped_start), + "Frame at start should equal frame at wrapped start" + ); + + assert!( + Arc::ptr_eq(&frame_end, &frame_wrapped_end), + "Frame at cycle end should equal frame at next cycle end" + ); +} + +#[test] +fn static_image_not_affected_by_animation_behavior_change() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + // Load a static PNG with FullAnimation behavior (should remain static) + let static_full_animation = load_bundled_image( + &image_cache, + &asset_cache, + "local.png", + Vector2I::new(512, 512), + FitType::Cover, + AnimatedImageBehavior::FullAnimation, + ); + + // Load the same static PNG with FirstFramePreview behavior (should remain static) + let static_preview = load_bundled_image( + &image_cache, + &asset_cache, + "local.png", + Vector2I::new(512, 512), + FitType::Cover, + AnimatedImageBehavior::FirstFramePreview, + ); + + // Both should be static images + assert!(matches!(static_full_animation.as_ref(), Image::Static(_))); + assert!(matches!(static_preview.as_ref(), Image::Static(_))); + + // Extract and verify they're the same image data + let Image::Static(full_static) = static_full_animation.as_ref() else { + unreachable!(); + }; + let Image::Static(preview_static) = static_preview.as_ref() else { + unreachable!(); + }; + + // Both should have the same dimensions + assert_eq!( + full_static.img.dimensions(), + preview_static.img.dimensions() + ); +} + +#[test] +fn animated_gif_shows_first_frame_preview_when_requested() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + // Request FirstFramePreview behavior for an animated GIF + let preview = load_bundled_image( + &image_cache, + &asset_cache, + "numbers-1000ms.gif", + Vector2I::new(16, 16), + FitType::Contain, + AnimatedImageBehavior::FirstFramePreview, + ); + + // Should get a static image (the first frame) + let Image::Static(_) = preview.as_ref() else { + panic!("Expected static image for FirstFramePreview"); + }; + + // But the underlying asset in asset_cache should still be the full AnimatedBitmap + let asset: AssetState = asset_cache.load_asset(AssetSource::Bundled { + path: "numbers-1000ms.gif", + }); + let AssetState::Loaded { data } = asset else { + panic!("Bundled asset should be available immediately!"); + }; + assert!(matches!(data.as_ref(), ImageType::AnimatedBitmap { .. })); +} + +#[test] +fn animated_image_remaining_delay_decreases_within_frame() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + let image = load_bundled_image( + &image_cache, + &asset_cache, + "numbers-1000ms.gif", + Vector2I::new(16, 16), + FitType::Contain, + AnimatedImageBehavior::FullAnimation, + ); + + let Image::Animated(animated) = image.as_ref() else { + panic!("Expected animated image"); + }; + + // Assumes the gif's first frame lasts at least 50ms. + let (_, remaining_at_start) = animated + .get_current_frame(0) + .expect("Should get frame at 0ms"); + let (_, remaining_at_50) = animated + .get_current_frame(50) + .expect("Should get frame at 50ms"); + + assert!( + remaining_at_50 < remaining_at_start, + "Remaining delay should decrease as we progress through a frame" + ); +} + +#[test] +fn animated_webp_also_advances_frames() { + let asset_cache = new_asset_cache(); + let image_cache = ImageCache::new(); + + let image = load_bundled_image( + &image_cache, + &asset_cache, + "animated.webp", + Vector2I::new(16, 16), + FitType::Contain, + AnimatedImageBehavior::FullAnimation, + ); + + let Image::Animated(animated) = image.as_ref() else { + panic!("Expected animated image"); + }; + + assert!( + animated.frames.len() > 1, + "WebP should have multiple frames" + ); + + let (frame_0, _) = animated + .get_current_frame(0) + .expect("Should get frame at 0ms"); + let (frame_near_end, _) = animated + .get_current_frame(animated.duration - 1) + .expect("Should get frame near end"); + + assert!( + !Arc::ptr_eq(&frame_0, &frame_near_end) || animated.frames.len() == 1, + "WebP animation should traverse different frames or only have one frame" + ); + + let (frame_wrapped, _) = animated + .get_current_frame(animated.duration) + .expect("Should get frame after one cycle"); + assert!( + Arc::ptr_eq(&frame_0, &frame_wrapped), + "Frame should wrap back to start after duration" + ); +}