From 659e22e9fce8a2d4dab7be9b5f0df4f13c212435 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Tue, 3 Nov 2026 10:42:21 +0700 Subject: [PATCH] update avatar --- Cargo.lock | 798 +++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + crates/core/src/avatar.rs | 35 +- docs/PLAN.md | 61 ++- examples/gallery/Cargo.toml | 1 + examples/gallery/src/main.rs | 166 ++++---- 6 files changed, 912 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9cbf8a..57b67bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,7 +249,7 @@ dependencies = [ "polling", "rustix", "slab", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -318,7 +318,7 @@ dependencies = [ "rustix", "signal-hook-registry", "slab", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -399,6 +399,29 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "aws-lc-rs" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "backtrace" version = "0.3.77" @@ -688,6 +711,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -715,6 +747,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1003,7 +1045,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1161,7 +1203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1393,6 +1435,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.34" @@ -1518,6 +1566,7 @@ dependencies = [ "gpui-native-layout", "gpui-native-theme", "gpui_platform", + "reqwest_client", ] [[package]] @@ -1537,8 +1586,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1562,9 +1613,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -1766,7 +1819,7 @@ dependencies = [ "serde", "toml 0.8.23", "windows 0.62.2", - "windows-registry", + "windows-registry 0.6.1", "zbus", ] @@ -2030,7 +2083,26 @@ dependencies = [ "windows 0.62.2", "windows-core 0.62.2", "windows-numerics 0.3.1", - "windows-registry", + "windows-registry 0.6.1", +] + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -2159,6 +2231,19 @@ dependencies = [ "http", ] +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + [[package]] name = "http_client" version = "0.1.0" @@ -2177,6 +2262,81 @@ dependencies = [ "url", ] +[[package]] +name = "http_client_tls" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed?rev=ec86047d67acd6fddd81392f44ed6ebcc6e7f531#ec86047d67acd6fddd81392f44ed6ebcc6e7f531" +dependencies = [ + "log", + "rustls", + "rustls-platform-verifier", + "webpki-roots", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -2412,6 +2572,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + [[package]] name = "itertools" version = "0.14.0" @@ -2427,6 +2593,36 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -2641,6 +2837,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + [[package]] name = "lyon" version = "1.0.19" @@ -2773,6 +2975,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2802,7 +3010,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2906,7 +3114,7 @@ dependencies = [ "objc2-core-foundation", "objc2-core-services", "walkdir", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -3386,6 +3594,12 @@ dependencies = [ "zvariant", ] +[[package]] +name = "openssl-probe" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1" + [[package]] name = "option-ext" version = "0.2.0" @@ -3579,7 +3793,7 @@ dependencies = [ "hermit-abi", "pin-project-lite", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -3792,6 +4006,62 @@ 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", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.21", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -3840,6 +4110,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ + "chacha20", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -3888,6 +4159,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rand_xorshift" version = "0.5.0" @@ -4116,6 +4396,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "reqwest_client" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed?rev=ec86047d67acd6fddd81392f44ed6ebcc6e7f531#ec86047d67acd6fddd81392f44ed6ebcc6e7f531" +dependencies = [ + "anyhow", + "bytes", + "futures", + "gpui_util", + "http_client", + "http_client_tls", + "log", + "regex", + "tokio", + "zed-reqwest", +] + [[package]] name = "resvg" version = "0.46.0" @@ -4142,6 +4439,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -4194,7 +4505,93 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -4248,6 +4645,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scheduler" version = "0.1.0" @@ -4302,6 +4708,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.3.0" @@ -4464,6 +4893,16 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simd_helpers" version = "0.1.0" @@ -4473,6 +4912,12 @@ dependencies = [ "quote", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simplecss" version = "0.2.2" @@ -4555,6 +5000,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "spin" version = "0.9.9" @@ -4690,6 +5145,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.14.0" @@ -4724,6 +5188,27 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "system-configuration" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501336eb7ba9e417300a6a0fa985721065467aa83a6dcf0422a8e43e4c0328fa" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "taffy" version = "0.15.0" @@ -4756,7 +5241,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -4889,7 +5374,48 @@ version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ + "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", ] [[package]] @@ -4984,6 +5510,33 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -5016,6 +5569,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -5039,7 +5598,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -5108,6 +5667,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca718471876992059f251e33aafc334346ada08042b7661dd1fe313a9bced557" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -5228,6 +5793,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -5299,6 +5873,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm_thread" version = "0.3.3" @@ -5342,6 +5929,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -5572,7 +6177,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -5588,7 +6193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core 0.57.0", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -5656,7 +6261,7 @@ dependencies = [ "windows-implement 0.57.0", "windows-interface 0.57.0", "windows-result 0.1.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -5783,6 +6388,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + [[package]] name = "windows-registry" version = "0.6.1" @@ -5800,7 +6416,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -5821,6 +6437,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -5839,6 +6464,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -5854,14 +6488,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -5897,48 +6548,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.15" @@ -5964,7 +6663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -6096,7 +6795,7 @@ dependencies = [ "tracing", "uds_windows", "uuid", - "windows-sys", + "windows-sys 0.61.2", "winnow 1.0.4", "zbus_macros", "zbus_names", @@ -6162,6 +6861,53 @@ dependencies = [ "yeslogic-fontconfig-sys", ] +[[package]] +name = "zed-reqwest" +version = "0.12.15-zed" +source = "git+https://github.com/zed-industries/reqwest.git?rev=33bc764aa15ff7b200bf7c93bd96e24878d53e14#33bc764aa15ff7b200bf7c93bd96e24878d53e14" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + [[package]] name = "zed-scap" version = "0.0.8-zed" diff --git a/Cargo.toml b/Cargo.toml index d06c09d..ef2c96c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ gpui = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fdd gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531", features = [ "font-kit", ] } +reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" } gpui-native-core = { path = "crates/core", default-features = false } gpui-native-data = { path = "crates/data", default-features = false } gpui-native-layout = { path = "crates/layout" } diff --git a/crates/core/src/avatar.rs b/crates/core/src/avatar.rs index b706044..2f3b553 100644 --- a/crates/core/src/avatar.rs +++ b/crates/core/src/avatar.rs @@ -1,6 +1,5 @@ use std::fmt::Display; -use gpui::prelude::FluentBuilder as _; use gpui::{ App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled as _, Window, div, @@ -27,6 +26,7 @@ pub struct Avatar { name: Option, picture: Option, size: Size, + fully_rounded: bool, style: StyleRefinement, } @@ -43,6 +43,7 @@ impl Avatar { name: None, picture: None, size: Size::default(), + fully_rounded: false, style: StyleRefinement::default(), } } @@ -56,7 +57,7 @@ impl Avatar { self } - /// The user's picture URL, if known. `None` clears it. + /// The user's picture URL. pub fn picture

(mut self, picture: Option

) -> Self where P: Into, @@ -64,6 +65,12 @@ impl Avatar { self.picture = picture.map(Into::into); self } + + /// Renders the avatar circular instead of the control radius. + pub fn fully_rounded(mut self, fully_rounded: bool) -> Self { + self.fully_rounded = fully_rounded; + self + } } impl Sizable for Avatar { @@ -81,7 +88,11 @@ impl gpui::Styled for Avatar { impl RenderOnce for Avatar { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); - let radius = theme.radius.control; + let radius = if self.fully_rounded { + theme.radius.full + } else { + theme.radius.control + }; let side = theme.sizes.resolve(self.size).avatar_size; let base = h_flex() @@ -90,24 +101,18 @@ impl RenderOnce for Avatar { .justify_center() .overflow_hidden() .size(side) - .text_size(side * 0.44) .bg(theme.colors.secondary) .rounded(radius) - .when_some( - self.name.clone().or(Some(self.id.clone())), - |avatar, label| avatar.role(Role::Image).aria_label(label), - ) + .role(Role::Image) + .aria_label(self.name.clone().unwrap_or_else(|| self.id.clone())) .refine_style(&self.style); match self.picture { - Some(url) => base.child( - gpui::img(gpui::SharedUri::from(url)) - .size_full() - .rounded(radius), - ), + Some(url) => base.child(gpui::img(url).size_full().rounded(radius)), None => base.child(PixelAvatar { seed: fnv1a(self.id.as_bytes()), side, + radius, style: self.style, }), } @@ -120,6 +125,7 @@ impl RenderOnce for Avatar { struct PixelAvatar { seed: u64, side: gpui::Pixels, + radius: gpui::Pixels, style: StyleRefinement, } @@ -132,7 +138,6 @@ impl gpui::Styled for PixelAvatar { impl RenderOnce for PixelAvatar { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); - let radius = theme.radius.control; let pattern = pattern(self.seed); let mut cells = Vec::new(); @@ -177,7 +182,7 @@ impl RenderOnce for PixelAvatar { .grid_rows(GRID_SIZE as u16) .size(self.side) .flex_none() - .rounded(radius) + .rounded(self.radius) .overflow_hidden() .bg(gpui::Hsla { a: 0.16, ..main }) .children(cells) diff --git a/docs/PLAN.md b/docs/PLAN.md index 08acca7..f6ff106 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1,6 +1,6 @@ # gpui-native-kit — Project Plan -Rev 8 (2026-11-01). Changes from rev 7: the signed migration step is removed — its §10 and §12 traces cleaned up; [signed](https://git.reya.info/reya/signed) stays the first downstream consumer whose screens drive §6.3 and §6.4, but porting the app stays app-side and off the kit roadmap. A new `layout` crate (§6.4, D26) bootstraps the application shell — left sidebar on, right sidebar off, center dock with drag-to-split panes — reversing the old "dock stays app-side" deferral; Phase H now covers it. The rev 7 rules stand: components are built **one element at a time behind a review gate** — each unit is designed, implemented, tested, shown in the gallery, and reviewed before the next starts (batch waves are gone). [gpui-kit `base`](https://github.com/longbridge/gpui-kit/tree/main/crates/base) is re-admitted as the **API-design reference**: its unstyled-control conventions (controlled state, one activation path for pointer + Enter + Space, semantic states, a11y labels) shape our component APIs, and small utilities may be adapted where they help. Everything else (empty-project reset, theme engine as the product, crate split, per-component features) stands. +Rev 9 (2026-11-03). Changes from rev 8: the layout crate (§6.4) is complete through L5 — dock with drag-to-split, titlebar + window controls, panel-declared sidebar auto-open — and the gallery is now the component browser: the app shell with the component catalog in its sidebar, every component a dock panel. Titlebar lessons landed in the theme and layout: `Theme.titlebar_height` (§5.5), and app-owned window dragging so a dragged tab never drags the window with it (L4.5). The `Sidebar` scrolls internally. The rev 8 rules stand: components are built **one element at a time behind a review gate** — each unit is designed, implemented, tested, shown in the gallery, and reviewed before the next starts (batch waves are gone). [gpui-kit `base`](https://github.com/longbridge/gpui-kit/tree/main/crates/base) is re-admitted as the **API-design reference**: its unstyled-control conventions (controlled state, one activation path for pointer + Enter + Space, semantic states, a11y labels) shape our component APIs, and small utilities may be adapted where they help. Everything else (theme engine as the product, crate split, per-component features) stands. ## 1. Vision @@ -25,8 +25,9 @@ Decision D15: we depend on no UI toolkit and port no wholesale subsystems. These ```toml gpui = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" } gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531", features = ["font-kit"] } + reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" } ``` - `font-kit` is required on macOS for glyph rasterization; Linux needs `wayland` and/or `x11`; Windows needs nothing. No `gpui-pre`, no forks, no `gpui-unofficial`. Toolchain 1.98.1 via `rust-toolchain.toml` (matches zed at the pinned rev; verified building on this machine). Bumping the pin is a deliberate act: re-verify, fix drift, note it in the bump commit message. + `font-kit` is required on macOS for glyph rasterization; Linux needs `wayland` and/or `x11`; Windows needs nothing. Apps that show remote images install a real client at startup — `application().with_http_client(Arc::new(reqwest_client::ReqwestClient::new()))` — because gpui defaults to a `NullHttpClient` and `img(http…)` silently loads nothing without one. No `gpui-pre`, no forks, no `gpui-unofficial`. Toolchain 1.98.1 via `rust-toolchain.toml` (matches zed at the pinned rev; verified building on this machine). Bumping the pin is a deliberate act: re-verify, fix drift, note it in the bump commit message. - **native-theme design (the blueprint we adapt, not a dependency):** sparse TOML-shaped `Theme` (Option fields) resolves into a fully populated `ResolvedTheme`; bundled presets; `AccessibilityPreferences`; thin per-toolkit connectors. Its GPUI connector targets gpui-component — a case study only. - **xdg-desktop-portal `Settings` (verified against the spec):** `org.freedesktop.appearance` standardizes `color-scheme` (u), `accent-color` ((ddd) RGB 0–1), `contrast` (u), `reduced-motion` (u), with `SettingChanged` — one DBus interface covering mode, accent, contrast, and reduce-motion across GNOME, KDE, and generic Linux. - **Omarchy theme contract (verified against the manual and the shipped `tokyo-night` theme):** active theme is `$HOME/.local/state/omarchy/current/theme/colors.toml` with `theme.name` beside it (legacy `~/.config/omarchy/current`; `current/theme` is a symlink into the theme set). `colors.toml` states `mode = "dark" | "light"` plus semantic keys — `accent`, `selection`, `muted`, `background`/`dark_background`/`darker_background`/`lighter_background`, `foreground`/`dark_foreground`/`light_foreground`/`bright_foreground`, and `red`/`yellow`/`orange`/`green`/`cyan`/`blue`/`magenta`/`brown` with `bright_*`; the ANSI `color0..15` format is an accepted alternative. @@ -54,7 +55,8 @@ gpui-native-kit/ # repo root = workspace root │ ├── layout/ # gpui-native-layout: application layout bootstrap + center dock (§6.4) │ └── editor/ # gpui-native-editor: created when the editor units need it (D12, §6.3 M15) └── examples/ - └── gallery/ # showcase; a page per component + theme/icon inspector + └── gallery/ # the component browser: the app shell with the + # catalog in its sidebar, every component a dock panel ``` Advanced crates (`data` for Tree/List/Tabs/Sidebar, `layout` for the application shell (§6.4), `charts`, `pickers`) are created on demand when their wave starts — not speculative scaffolding. @@ -153,16 +155,15 @@ graph TD Omarchy `colors.toml` maps almost 1:1: `lighter_background` → `surface`, `dark_background`/`darker_background` → elevation depth, `dark_foreground` → muted foreground, `accent` → `primary`/`accent`/`ring`, `red` → `destructive`, `green`/`yellow`/`orange` → success/warning. Every mapping lives in one module; never literals. Tests assert finite, distinct, semantically sane tokens for every preset × mode. -The set stays role-minimal and grows one concept at a time, driven by the unit that needs it. Known planned additions: +The set stays role-minimal and grows one concept at a time, driven by the unit that needs it. Landed additions, each with its consumer: -- ~~**Overlay scrim + shadow/elevation tokens**~~ — ✅ landed with unit 11 (2026-10-29): `colors.overlay` (translucent-black scrim, denser in dark; TOML-overridable; no platform states one), `Radius.overlay` (10 pt), `Elevations.popover`/`dialog` (builder → `Vec`), and `Motion.overlay` (200 ms enter transition). Popover consumes elevation + motion; the scrim waits for Dialog. **Per-platform stacks (2026-11-01, D24)**: `Elevation` became a two-layer key + ambient stack and `resolve` picks it per `SourceKind` — macOS soft and layered (key shadow at 12% alpha), Windows tight and offset, Linux compact and darker; dark mode shares one stack — deleting the Alert's light-mode alpha hack -- ~~**Scrollbar thumb token + metrics**~~ — ✅ landed with the ScrollArea unit (2026-10-29): `colors.scrollbar_thumb` (translucent over content: black in light, light gray in dark; TOML-overridable) and `Theme.scrollbar` (`width` 8 pt, `inset` 2 pt, `min_thumb` 24 pt). -- ~~**`success` / `warning` roles**~~ — ✅ landed with Toast (2026-10-29): `colors.success`/`warning` (+ foregrounds; light and dark variants; TOML-overridable; no platform states them) and derived `Ramps.success`/`warning` via the OKLCH engine, matching the destructive treatment. **`info` joined them (2026-10-31)** — the fourth severity, same treatment (`colors.info` + foreground + `Ramps.info`, neutral sky blue, no platform states it, TOML `info =`). Trigger: the Alert's Info severity was painting from the primary ramp, so a warm platform accent (GNOME red, Omarchy red themes) made Info read as Danger; the token keeps Info calm however the accent turns out. Severity tokens now use the same picked value for the ramp and the token (the success/warning tokens used to re-pick with the system slot dropped, diverging from their own ramps if a platform ever stated one). -- **Motion tokens** — after the reverted framework attempt (2026-10-30), motion stays minimal: `Motion.overlay` (200 ms) is the only token. Components reach for gpui's own easings and `SpringConfig` inline (D21); a token is added only when a third component shares the same value (D16). TOML `[motion]` overrides and per-platform motion presets wait for a real need. ~~The reduce-motion forwarding fix~~ ✅ landed (pass a, 2026-10-30): `reapply` calls `cx.set_reduce_motion(resolved.accessibility.reduce_motion)`, so gpui's animation elements snap under the OS setting. -- ~~**Ghost hover token**~~ — ✅ landed with the dock tabs (2026-11-02): `colors.muted_hover` (light zinc-200 / dark zinc-700, one step past `muted`; TOML `muted_hover =`; no platform states a hover wash). Trigger: the ghost Button's hover painted `muted` — the same token the selected pill fills with — so hovering a close control on a selected tab was invisible. `Button::Ghost` and the selected pill hover from `muted_hover` now. -- ~~**`knob` / control-fill role**~~ — resolved without a token: Slider (unit 7) shipped on the Switch's borrowed roles (`primary_foreground` fill, `border` off-track); a dedicated role waits for a third consumer. +- **Overlay scrim + elevation + motion tokens** (unit 11, 2026-10-29): `colors.overlay`, `Radius.overlay`, `Elevations.popover`/`dialog`, `Motion.overlay` (200 ms) — with per-platform two-layer shadow stacks picked per `SourceKind` (D24, 2026-11-01). +- **Scrollbar thumb token + metrics** (ScrollArea, 2026-10-29): `colors.scrollbar_thumb`, `Theme.scrollbar` (`width` 8 pt, `inset` 2 pt, `min_thumb` 24 pt). +- **`success` / `warning` / `info` severities** (Toast 2026-10-29; `info` 2026-10-31, D22): tokens + foregrounds + OKLCH ramps, no platform states them, TOML-overridable — so a warm platform accent never makes Info read as Danger. +- **`colors.muted_hover`** (dock tabs, 2026-11-02): one step past `muted`; the ghost Button's hover used to paint the same `muted` a selected pill fills with. +- **`Theme.titlebar_height`** (2026-11-03, §5.5): the height the window's titlebar strips draw at, stated per platform where the OS declares one. -Controls through unit 10 paint from existing roles and ramps. +Resolved without tokens: Slider rides the Switch's borrowed roles (no knob role — no third consumer); motion stays exactly `Motion.overlay` (D21), with the reduce-motion forwarding fix landed 2026-10-30 (`reapply` calls `cx.set_reduce_motion`). Controls through unit 10 paint from existing roles and ramps. ### 5.3 Platform matrix @@ -183,7 +184,7 @@ Every target supplies one accent color (or a stated ramp). Where the platform st ### 5.5 Native metrics -Native feel is half geometry. Per-platform tables for: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font fallback lists resolved through `.SystemUIFont` (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans — no fonts bundled), focus-ring policy (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline), menu metrics (popup elevation landed 2026-11-01 as static per-platform profiles, D24). Motion stays per-component timing on gpui's own primitives; when a platform source needs to state it (macOS NSPopover-class timing, Fluent's decelerate curve, GNOME/KDE shell timings), it states `Motion.overlay` first and further tokens arrive per the role-minimal rule. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots (macOS in Phase B, the rest in Phase F). +Native feel is half geometry. Per-platform tables for: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font fallback lists resolved through `.SystemUIFont` (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans — no fonts bundled), focus-ring policy (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline), menu metrics (popup elevation landed 2026-11-01 as static per-platform profiles, D24). **`titlebar_height` landed (2026-11-03)**: `Theme.titlebar_height` — the height the window's titlebar strips draw at. Windows states 32 px (the Win32 caption at 100% scaling, via `SystemTheme.titlebar_height`); macOS and Linux derive `max(medium control_height, 34 px)` — the height custom titlebars draw at there, per the pinned zed reference. The dock's pane bar centers its tabs in it. Motion stays per-component timing on gpui's own primitives; when a platform source needs to state it (macOS NSPopover-class timing, Fluent's decelerate curve, GNOME/KDE shell timings), it states `Motion.overlay` first and further tokens arrive per the role-minimal rule. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots (macOS in Phase B, the rest in Phase F). ### 5.6 Custom themes @@ -223,7 +224,7 @@ Each row is one review unit: build it, show it in the gallery, review, then move | 11 | core | Popover | ✅ Landed (2026-10-29). First overlay. Controlled `open` + `on_change` (Rc-shared handler); `trigger`/`content` element closures; panel = surface + `Radius.overlay` + `Elevations.popover` + border, role Dialog with label; `anchored()` + `deferred()` (window-overflow flip for free); enter fade via gpui `with_animation` at `Motion.overlay` (respects reduce-motion); claims keyboard focus while open; escape and click-outside dismiss (outside clicks swallowed). No scrim — native macOS popover model; the overlay token waits for Dialog. Known v1 gaps: no exit animation, trigger aria-expanded left to the app | | 11+ | core | Tooltip ✅ (2026-10-29), Dialog + AlertDialog ✅ (2026-10-29), Toast ✅ (2026-10-29), Alert + Toast rework ✅ (2026-10-31) | One unit each, same gate. Tooltip rides gpui's own `.tooltip()` lifecycle (hover delay, anchoring, dismissal — all framework-owned); ours is the styled view (`surface` + border + `Radius.sm`, small text) plus a `Tooltip::text` closure and a Button passthrough. Dialog + AlertDialog (one component, `alert(true)` switches semantics): scrim (`colors.overlay`, occluding) + centered panel (surface, `Radius.overlay`, `Elevations.dialog`), role Dialog/AlertDialog, focus claimed while open; modal kind closes on escape + scrim click, alert kind is inert to both (the user answers through the actions); actions footer is caller-built elements closing through owner state. **The 2026-10-31 rework split the old Toast in two (D23):** `Alert` (`alert` feature) — a persistent, fully controlled callout banner rendered inline where the app places it: severity icon (`Ramps..base` on a `surface` + `border` + `Radius.overlay` card with the platform popover shadow (D24)), title and message both optional (a message-only banner paints it as the primary line), close button only when `on_dismiss` is set, reporting intent instead of hiding anything. A pushed, macOS-style stacking `Alerts`/`AlertsView` (collapse-slivers + hover-unroll + slide animations) was tried and rolled back the same day — inline-only won. The reworked `Toast` (`toast` feature) — **one at a time** (pushing replaces the one on screen), a succinct severity-less pill at the bottom center: optional icon + one-line truncating content + one optional action (`ToastAction`, the suggested-action pill) + an always-present close button, laid out as content, small gap, action, large gap, close; fades in and out (`Motion.overlay` each — exits via a store-held exiting slot whose handler runs once the fade completes). The store keeps GPUI-executor timers (`Toast::DURATION` 5 s, zero = sticky). Toast landed the deferred `success`/`warning` tokens (§5.2); Alert is the `info` token's consumer (D22). Sheet dropped from the roadmap | | 14 | core + theme | Motion (per component) | ✅ Landed in five passes (2026-10-30), replacing the reverted same-day framework attempt — no token tower, no `motion` module, no ghost panels; just gpui's own primitives used inline, one separately reviewed pass per component: (a) theme — `reapply` forwards `reduce_motion` via `cx.set_reduce_motion`, so gpui snaps every animation under the OS setting; (b) Switch — the thumb absolute-positions in the track and slides on `with_spring` (inline `SpringConfig::new(500, 35, 1)`, `Pixels` target, mounts at its target so the initial render never animates; velocity survives mid-flight retargets); (c) Toast — each toast slides up 8 px into its stack slot on `with_animation` at `Motion.overlay` with `ease_out_quint` (margin-driven, so only the entering toast moves; keyed per entry so the clock survives unrelated stack re-renders); (d) Dialog — the scrim enters with the panel (same timing and easing, its own `dialog-scrim-enter` animation id so the clocks start in the same frame); (e) Progress — the indeterminate sweep caps at 30 fps via `with_max_fps`. Overlay enter fades already existed from units 11/12. Exits and hover washes stay deferred — they were the complexity sink of the reverted attempt (§ deferred list) | -| then | data (new) | List ✅ (2026-10-30), Tree ✅ (2026-10-30), Tabs ✅ (2026-10-30), Sidebar ✅ (2026-10-30) | Created on demand, same gate. Table dropped from the roadmap. List: controlled single-selection rows (`selected: Option` + `on_change`); click selects and focuses the list, arrows/home/end move selection skipping disabled rows and never wrapping (native list behavior), hover is paint-only, click focus rides a keyed `FocusHandle` namespaced by the list id; roles List/ListItem with `aria_selected` and position/size-of-set; internal scrolling left to the caller's ScrollArea; `sheet` feature dropped from core as dead config. Tree: the caller flattens its tree into depth-annotated `TreeItem`s (bezel's model — the component never walks a tree); the caller owns expansion and receives toggles via `on_disclosure(index)` (chevron click, or left/right on the selected row); same controlled selection and row paint as List; roles Tree/TreeItem with `aria_selected`/`aria_level`/`aria_expanded`/set-size; indent guides deferred. Tabs: a controlled strip only — `selected: usize` + `on_change`; the panel is the app's to render; active tab marked by an underline on the strip's hairline (bezel's model); arrows/left-right/home/end switch tabs with automatic activation, skipping disabled and never wrapping; re-selecting the active tab reports nothing; roles TabList/Tab with `aria_selected`; no wrap, no segmented-pill variant yet. Sidebar: List with section headers — `SidebarEntry::header(label)` / `row(label).icon().detail().disabled()`; headers are never selectable, selection indices address the flat entry list; same selection/keyboard/paint rules as List; roles List/ListItem (a sidebar is a list to accesskit, like macOS AXList); selection stays filled when unfocused (native) | +| then | data (new) | List ✅ (2026-10-30), Tree ✅ (2026-10-30), Tabs ✅ (2026-10-30), Sidebar ✅ (2026-10-30) | Created on demand, same gate. Table dropped from the roadmap. List: controlled single-selection rows (`selected: Option` + `on_change`); click selects and focuses the list, arrows/home/end move selection skipping disabled rows and never wrapping (native list behavior), hover is paint-only, click focus rides a keyed `FocusHandle` namespaced by the list id; roles List/ListItem with `aria_selected` and position/size-of-set; internal scrolling left to the caller's ScrollArea; `sheet` feature dropped from core as dead config. Tree: the caller flattens its tree into depth-annotated `TreeItem`s (bezel's model — the component never walks a tree); the caller owns expansion and receives toggles via `on_disclosure(index)` (chevron click, or left/right on the selected row); same controlled selection and row paint as List; roles Tree/TreeItem with `aria_selected`/`aria_level`/`aria_expanded`/set-size; indent guides deferred. Tabs: a controlled strip only — `selected: usize` + `on_change`; the panel is the app's to render; active tab marked by an underline on the strip's hairline (bezel's model); arrows/left-right/home/end switch tabs with automatic activation, skipping disabled and never wrapping; re-selecting the active tab reports nothing; roles TabList/Tab with `aria_selected`; no wrap, no segmented-pill variant yet. Sidebar: List with section headers — `SidebarEntry::header(label)` / `row(label).icon().detail().disabled()`; headers are never selectable, selection indices address the flat entry list; same selection/keyboard/paint rules as List; roles List/ListItem (a sidebar is a list to accesskit, like macOS AXList); selection stays filled when unfocused (native). **Follow-up (2026-11-03):** the Sidebar scrolls internally — entries sit in a `ScrollArea` whose handle lives in element state (the overlay thumb paints; the offset survives re-renders); the gallery's 29-entry catalog outgrew the fixed region. `sidebar` pulls `core/scroll_area` | | then | pickers or data | Calendar/DatePicker, TimeField, ColorPicker | Decide at unit time. | | editor | editor (new) | Text/code editor, syntax highlighting (D12) | Same gate. | @@ -233,7 +234,7 @@ Deferred until a real need: window-wide text selection, overlay exit animations **Landed** (2026-10-29): the `Size` enum (`XSmall` / `Small` / `Medium` default / `Large` / `Custom(Pixels)`), the `Sizable` trait (`with_size(impl Into)` + `xsmall()` / `small()` / `large()`), and the `SizeTable` of per-class control metrics (height, padding-x, text size, icon/indicator size, avatar size) now live where they belong — `Size`/`SizeTable`/`ControlMetrics` in the theme (they are native-metric values, §5.5), the trait in core's ungated `common`. -Our divergence from gpui-kit held: the per-size metrics resolve from **the theme's size table at paint time**, not from constants in the component layer. All ten controls implement `Sizable`; `Size::Custom(side)` derives the other metrics from the side length. Still open, when a real need arrives: TOML overrides for the size table in `ThemeDefinition`, and per-platform size tables from the Phase F native-metrics work. +Our divergence from gpui-kit held: the per-size metrics resolve from **the theme's size table at paint time**, not from constants in the component layer. All ten controls implement `Sizable`; `Size::Custom(side)` derives the other metrics from the side length. An `XSmall` class was tried (2026-11-03) and removed — three classes cover the components so far; explicit sides are a caller style refinement (`Styled::size`). Still open, when a real need arrives: TOML overrides for the size table in `ThemeDefinition`, and per-platform size tables from the Phase F native-metrics work. ### 6.2 Input completion (unit 10 finishing pass) — ✅ landed (2026-10-29) @@ -269,7 +270,7 @@ Wave M1 — each through the review gate: | M5 | core | Empty state ✅ Landed (2026-11-01) | inbox, issues, search misses | bezel: icon + headline + one line of hint. Shipped as `EmptyState::new(headline)` with optional `.icon(role)`, `.hint(text)`, and `.action(element)` — no id (nothing interactive), no size classes (content, always medium metrics); the illustration is the component's own `icon_size × 2` decoration, muted like the hint, `line_height 1.3` on the headline, everything centered. Plain `empty_state` feature, no deps | | M6 | core | Password input ✅ Landed (2026-11-01) | identity unlock + `nsec` import passphrase | Lifts §6.2's "password masking on first need" — the need arrived. `InputState::set_password(bool)` masks the painted line one bullet per grapheme (paint-only: the buffer, undo, and caret all stay real; a mask map of grapheme byte offsets converts between the buffer and the painted display for hit-testing, the caret, selection, the IME underline, and `bounds_for_range`). Masked fields refuse copy and cut so the secret never reaches the clipboard; paste, editing, and reveal stay available. No element builder — the flag is state, like read-only. **Follow-up (2026-11-01):** a built-in reveal toggle — a ghost icon-only Button (`IconRole::Eye`/`EyeOff`, native glyphs per platform) pinned inside the field's right edge, shown on every password field. The state now tracks the field kind (`set_password`/`is_password`) and its visibility (`set_revealed`/`is_revealed`) separately, so toggling reveal never changes the field's kind; the paint reserves the button's corner (`right_padding` threaded through `paint`/`store_layout`, with a content mask so text never runs under the icon). The reveal press stops propagation so it never places the caret. The `input` feature now depends on `button` | | M7 | input | Multiline textarea ✅ Landed (2026-11-01) | discussion composer, patch + commit messages | bezel: fixed rows or min/max auto-grow; gpuikit: rows on InputState. Reuses Input's buffer; D12's editor crate stays separate. Shipped as a state mode — `InputState::set_multiline(bool)` — sharing the single-line machinery: shaping moved to gpui `shape_text`, which wraps to the field's inner width, and the paint layout gained a visual-row table (byte range + y per wrapped row) that the caret, selection, IME underline, hit-testing, triple-click and `bounds_for_range` all consume; `scroll_offset` became 2-D (x single-line, y multiline) with scroll-to-caret per axis. Height = the ambient line-height ratio × the element's `rows(usize)` (default 4); content taller than the field scrolls, and the field stops wheel propagation only while overflowing (min/max auto-grow deferred). Enter inserts a newline and modifier+Enter submits — `SubmitOn::ModifiedEnter` is the default, `SubmitOn::Enter` plus the `InsertNewline` shift-enter binding gives chat-style enter-submit — lifting §6.2's deferral. Up/down (+ shift variants) move between visual rows with a goal column; triple-click selects the clicked row; the sanitizer is mode-aware (multiline keeps newlines, drops `\r`; single-line unchanged), and the mask map gained per-grapheme display lengths so masked fields survive line breaks. No theme changes — all geometry derives from existing tokens. 7 headless tests join `tests/input.rs` | -| M8 | core | Avatar image | profile pictures in rows and panel tabs | lifts the deferred image slot: gpui `img` + initials fallback (signed adds its pixel-art fallback app-side) | +| M8 | core | Avatar image ✅ Landed (2026-11-03) | profile pictures in rows and panel tabs | Lifts the deferred image slot — with the pixel-art fallback the row originally left to signed (the initials placeholder never shipped). `Avatar::new(id).name(..).picture(Option)`: the picture renders through gpui `img` on a `secondary` container, `Role::Image` with the aria label from name-or-id; a failed download paints the container blank — the pixel art is the **no-picture** fallback, matching signed. The fallback adapts signed's pixel avatar (D15/D20): FNV-1a of the id seeds an xorshift PRNG that draws a mirror-symmetric 8×8 grid (1-cell margin, 42% fill, 25% shade cells, ≥5 left-half cells); the hue rotates the accent's OKLCH hue per seed, the shade cells lighten in dark mode and darken in light, the tray fills at 16% alpha — deterministic across runs and platforms, colored only from theme-derived OKLCH. Sizes ride the size table's `avatar_size` (`Sizable`). 4 pattern tests (mirror symmetry, empty outer ring, determinism + minimum fill, hash stability); the gallery panel shows a loaded image, the seeded fallback, and both sizes. The gallery also installs zed's `reqwest_client` at startup (`with_http_client`) — without it gpui defaults to a `NullHttpClient` and remote pictures never load (§3). **Follow-up (2026-11-03):** `.fully_rounded(bool)` renders the avatar circular (`Radius.full` instead of the control radius — threaded through the picture, the pixel grid, and the tray) | Wave M2 — completes the surface: Field wrapper (M9: label/description/error around any control — the settings dialog; lifts another §6.2 deferral), Tag (M10: removable GRASP-server and relay entries), Status dot (M11: sync-drift beads on rows), Combobox (M12: type-to-filter pickers; gpuikit's `on_create`/`keep_unmatched` blur modes are worth copying, adopt that), Command palette (M13: filtered actions — gpuikit keeps the matcher caller-owned, adopt that), VirtualList in `data` (M14: a thin `uniform_list` binding for commit histories and the inbox), and the Spinner (M18: the indeterminate/determinate loading ring — `Tab::busy`'s placeholder ring in the `Pill` tab waits on it, as do async buttons and refresh states; picked up next time a consumer lands, before signed wires repo loading into dock tabs). @@ -279,7 +280,7 @@ Survey notes — worth adopting later, not now: bezel's hover card (profile prev ### 6.4 The layout crate (`crates/layout`) — application shell bootstrap -A new crate (D18: advanced crates are created on demand, not speculatively) that bootstraps the application layout the way zed's workspace and gpui-kit's dock do, with a deliberately smaller API (D26): a left sidebar (on by default), a right sidebar (off by default), and a center dock whose panes the user can drag and split. A panel can declare that it needs the right sidebar; activating it opens the sidebar automatically. [signed](https://git.reya.info/reya/signed)'s `crates/dock` — a gpui-component `DockArea` behind a custom skin — is the usage reference and the cautionary tale: its renderer seams, serialization placeholders, and zoom/collapse modes mark the complexity we skip. gpui-kit's dock and zed's editor dock are read as API-design references only (D15, D20). Facade feature `layout`; the crate depends on `core` and composes `data` — the L3 header decision, revised when L4 landed: pane headers compose the data crate's `Tabs` in its `Pill` variant (the signed dock tab — pills on a chrome-less bar: `[lead element][name][wide gap][togglable busy slot][close control]`; the bar carries no background or border because it doubles as the titlebar strip in L4.5), with `Tab::closeable` + `Tabs::on_close` for close controls, `Tab::lead` for avatar-or-custom leading elements, `Tab::busy` for the loading slot, and a `Tab::wrap` seam so the dock attaches its drag payload to the rendered tab; data's `Tabs` keeps the underline design as the default. A `window_frame` module wraps a window root in the client-side frame the OS owes — border, `Radius::overlay` corners, dialog-elevation shadow, resize bands — under `Decorations::Client` and passes through under `Server` (adapted from bezel's `window::frame` and gpui-kit's `WindowBorder`, simplified; the window must open transparent). New `IconRole`s (panel-left/right toggles, split directions) land in the icons crate with the units that need them. Everything is theme-baked per §4.3 — no style parameters, no renderer/skin seam. +A new crate (D18: advanced crates are created on demand, not speculatively) that bootstraps the application layout the way zed's workspace and gpui-kit's dock do, with a deliberately smaller API (D26): a left sidebar (on by default), a right sidebar (off by default), and a center dock whose panes the user can drag and split. A panel can declare a right-sidebar **view**; activating it points the sidebar at that view and the dock asks the app to open the sidebar. [signed](https://git.reya.info/reya/signed)'s `crates/dock` — a gpui-component `DockArea` behind a custom skin — is the usage reference and the cautionary tale: its renderer seams, serialization placeholders, and zoom/collapse modes mark the complexity we skip. gpui-kit's dock and zed's editor dock are read as API-design references only (D15, D20). Facade feature `layout`; the crate depends on `core` and composes `data` — the L3 header decision, revised when L4 landed: pane headers compose the data crate's `Tabs` in its `Pill` variant (the signed dock tab — pills on a chrome-less bar: `[lead element][name][wide gap][togglable busy slot][close control]`; the bar carries no background or border because it doubles as the titlebar strip in L4.5), with `Tab::closeable` + `Tabs::on_close` for close controls, `Tab::lead` for avatar-or-custom leading elements, `Tab::busy` for the loading slot, and a `Tab::wrap` seam so the dock attaches its drag payload to the rendered tab; data's `Tabs` keeps the underline design as the default. A `window_frame` module wraps a window root in the client-side frame the OS owes — border, `Radius::overlay` corners, dialog-elevation shadow, resize bands — under `Decorations::Client` and passes through under `Server` (adapted from bezel's `window::frame` and gpui-kit's `WindowBorder`, simplified; the window must open transparent). New `IconRole`s (panel-left/right toggles, split directions) land in the icons crate with the units that need them. Everything is theme-baked per §4.3 — no style parameters, no renderer/skin seam. Units, each through the review gate: @@ -290,7 +291,7 @@ Units, each through the review gate: | L3 | layout | Dock ✅ Landed (2026-11-02) | The center pane tree, controlled: `DockState` holds the `Pane { tabs, active }` / `Split { axis, ratio, a, b }` tree plus a registry of `PanelDescriptor`s (`id` + title + optional icon + a view builder called once per open; the built view is retained, so panel state survives tab switches). `open` / `activate` / `close` / `close_others` / `close_pane` / `split` / `move` are plain state ops keyed by panel id — opening an open panel re-activates it, one instance per descriptor for now, and `active_panel()` reports what the active pane shows. Any op emptying a pane collapses it: the pane detaches and single-child splits are replaced by the surviving subtree, so only the root can sit empty. Split seams are `ResizeHandle`s (arrow keys move them for free); the ratio lives in the state and the seam turns pixel deltas into fractions against a per-split span that a canvas captures into dock-namespaced keyed state each frame — double-click resets to 0.5, both sides clamp at 10% of the split. Panes render a `strip_height` header row composing the data crate's `Tabs` (revised in L4 — see below), `TabList`/`Tab`/`TabPanel` roles, and a core `ContextMenu` on the header — Close / Close Others / Close Pane; the active tab's view fills the pane. Feature `dock` pulls core `button` + `context_menu` and re-uses `resize_handle`; 7 headless tests in `crates/layout/tests/dock.rs` (open/activate, tab clicks, neighbor-on-close, split + collapse, seam drag/reset/clamp with a 1 px flex-rounding tolerance, merge-by-move, close-others/close-pane + reopen); the gallery's shell center hosts a dock with demo panels and open/split buttons | | L4 | layout | Dock drag-to-split ✅ Landed (2026-11-02) | Dragging a tab uses gpui's own drag system: the dock attaches `on_drag` through data `Tabs`' `Tab::wrap` seam with a `PanelDrag { panel }` payload (ghost = a small bordered title card). While `App::has_active_drag()`, each pane's content mounts five absolute drop zones — four edge bands that call the new `DockState::split_panel_at(panel, anchor, direction)` (a split beside the *target* pane, not the dragged tab's own; the empty source still collapses) and a center zone that calls `move_panel`. Zones paint a `selection` wash via `drag_over::` while hovered and are mounted only during a drag, so content clicks are never blocked; gpui cancels the drag on a mouse-up over nothing, which unmounts them. Dropped tabs land active in their new pane. Same pass: pane content renders through `AnyView::cached` (`StyleRefinement` absolute + size full, the gpui-kit pattern) so an un-notified panel never re-renders on dock frames; pane headers moved from hand-rolled tabs to data `Tabs` (`Pill` variant — renamed from `Card` and reshaped to signed's exact tab: leading element via `Tab::lead`, wide title-to-controls gap, `Tab::busy` loading slot standing in with a quiet ring until the Spinner control lands, a ghost core `Button` close control (the first data → core dependency edge, feature-gated: `tabs` pulls `core/button`), and the pill radius is the button radius, not `full`; the pill bar is chrome-less because the dock's tab strip doubles as the L4.5 titlebar), which now ships the two designs (default stays `Underline`); tab strips carry `{tabs-id}-tab-{label}` / `{tabs-id}-close-{label}` debug selectors for headless tests. 10 headless tests in `crates/layout/tests/dock.rs` — the new ones cover a real simulated drag (mouse down → threshold move → drag starts → drop on a right edge band → split) and `split_panel_at`'s target-side placement; the rest carried over with the new tab selectors | | L2.5 | layout | window frame ✅ Landed (2026-11-01) | `window_frame::frame(child, window, cx)` — the client-side window frame for windows the OS does not decorate: under `Decorations::Server` (macOS, Windows, KDE, X11) it passes the child through untouched; under `Decorations::Client` (Wayland/GNOME) it insets the content, draws the hairline border and `Radius::overlay` corners (tiled sides stay square and lose their band), casts the dialog elevation's shadow dimmed to 0.7× when the window is inactive, and turns a 4 px band inside each live edge into resize hits (`start_window_resize`, edge cursors; a maximized window counts as flush everywhere). Requires a transparent window background, and must be the window's root — the bands hit-test against `Window::window_bounds`. **The `Shell` applies it by default**, so the module ships ungated — no separate feature. Adapted from bezel's `window::frame` + gpui-kit's `WindowBorder` (D15/D20), simplified to one inset band and theme-owned tokens | -| L4.5 | layout | Titlebar + window controls ✅ Landed (2026-11-02) | The strips are the window's titlebar: every strip (shell sidebars, dock pill bar) marks itself `WindowControlArea::Drag` and double-click-maximizes (`titlebar_double_click` on macOS, `zoom_window` on Linux; Windows does it natively through the caption hit test). New `window_controls` module (feature `window_controls`, pulled by `shell` and `dock`): a `WindowControls` element rendered at the trailing end of the right-hand strip — the right sidebar's strip when visible, else the dock pill bar via `Dock::window_controls(bool)`. **Windows**: caption buttons (36 px wide, Segoe codepoints through the icons crate — new `WindowMinimize`/`WindowMaximize`/`WindowRestore` roles, E921/E922/E923 verified against zed's own table) marked `WindowControlArea::Min/Max/Close` so the OS owns the clicks and snap layouts; maximize flips to restore via `is_maximized`; capability-gated by `Window::window_controls()` + `is_minimizable`/`is_resizable`; close hovers the platform red (a platform convention const, like the Segoe codepoints). **Linux**: Adwaita circular buttons (the platform dress, hand-rolled like zed's) under `Decorations::Client` only, clicking `minimize_window`/`zoom_window`/`remove_window`. **macOS** draws nothing — system traffic lights; the shell reserves `TRAFFIC_LIGHT_PAD` (78 px, Tahoe SDK) in the left sidebar strip's leading edge, and `Dock::traffic_light_pad(bool)` pads the pill bar when no left sidebar sits over the lights. The gpui dispatch fact that makes this safe: Windows forwards `WM_NCLBUTTONDOWN` into gpui's normal event stream, so buttons inside a Drag-marked strip still get their clicks, and only unconsumed presses fall through to native caption behavior. Adapted from zed's `platform_title_bar` (D15/D20); the gallery wires both dock flags from `LayoutState`. 2 platform-gated headless tests (macOS pad shifts the pill bar's tab by the pad width; controls render only under the flag on their platforms); CI feature loop gained `window_controls` | +| L4.5 | layout | Titlebar + window controls ✅ Landed (2026-11-02) | The strips are the window's titlebar: every strip (shell sidebars, dock pill bar) marks itself `WindowControlArea::Drag` and double-click-maximizes (`titlebar_double_click` on macOS, `zoom_window` on Linux; Windows does it natively through the caption hit test). New `window_controls` module (feature `window_controls`, pulled by `shell` and `dock`): a `WindowControls` element rendered at the trailing end of the right-hand strip — the right sidebar's strip when visible, else the dock pill bar via `Dock::window_controls(bool)`. **Windows**: caption buttons (36 px wide, Segoe codepoints through the icons crate — new `WindowMinimize`/`WindowMaximize`/`WindowRestore` roles, E921/E922/E923 verified against zed's own table) marked `WindowControlArea::Min/Max/Close` so the OS owns the clicks and snap layouts; maximize flips to restore via `is_maximized`; capability-gated by `Window::window_controls()` + `is_minimizable`/`is_resizable`; close hovers the platform red (a platform convention const, like the Segoe codepoints). **Linux**: Adwaita circular buttons (the platform dress, hand-rolled like zed's) under `Decorations::Client` only, clicking `minimize_window`/`zoom_window`/`remove_window`. **macOS** draws nothing — system traffic lights; the shell reserves `TRAFFIC_LIGHT_PAD` (78 px, Tahoe SDK) in the left sidebar strip's leading edge, and `Dock::traffic_light_pad(bool)` pads the pill bar when no left sidebar sits over the lights. The gpui dispatch fact that makes this safe: Windows forwards `WM_NCLBUTTONDOWN` into gpui's normal event stream, so buttons inside a Drag-marked strip still get their clicks, and only unconsumed presses fall through to native caption behavior. Adapted from zed's `platform_title_bar` (D15/D20); the gallery wires both dock flags from `LayoutState`. 2 platform-gated headless tests (macOS pad shifts the pill bar's tab by the pad width; controls render only under the flag on their platforms); CI feature loop gained `window_controls`. **Amended (2026-11-03, app-owned dragging):** the gallery hit the macOS trap — with `appears_transparent` alone, AppKit still owns the native titlebar band and dragged the window from a tab press (the `Drag` marking is a no-op on macOS at this rev; `on_hit_test_window_control` is unimplemented there, unlike Windows). Windows now open with `app_owns_titlebar_drag: true` and the new `titlebar_strip` helper carries the strip behavior — drag-to-move (guarded by `has_active_drag`, so a dragged tab's session always wins), double-click maximize, and the `Drag` area — applied to the sidebar strips and to the dock bar's dead-zone span after the tabs, never to the tabs themselves (signed's pattern). The pane bar's height reads `Theme.titlebar_height` (§5.5), tabs centered in it | | L5 | layout | Sidebar auto-open ✅ Landed (2026-11-02) | Panels declare a right-sidebar **view**, not a flag: `PanelDescriptor::right_sidebar(builder)` — the view builds once when the panel opens (retained alongside the panel view, so its state survives too) and lives on the open tab. Activating a panel that has one points the sidebar at that view and runs the app-registered `DockState::on_open_right_sidebar(handler)` so the app opens it — visibility stays on the shell's `LayoutState`, the dock only asks. Panels without a view leave the sidebar showing the last view (sticky — no yanking on tab switches); closing the displayed panel's tab falls back to the first remaining tab's view, else blank. The sidebar renders through a new `DockSidebar` element (the cached-view pattern), which the app places in `Shell::right_sidebar`. Gallery: Overview and Inbox declare details views, Settings doesn't — switching to Settings keeps the sidebar, closing a details tab falls back. 2 headless tests (activation fires the callback and renders the panel's view; sticky-keep on view-less panels + close-fallback) | Deferred until a real need: layout persistence/serialization, pane zoom (maximize one pane), a bottom dock, collapse-to-edge docks, floating windows, drag between windows. @@ -306,8 +307,8 @@ Deferred until a real need: layout persistence/serialization, pane zoom (maximiz | E — overlays ✅ | Popover (11), ScrollArea (11.5), Menu (12), Tooltip + Dialog/AlertDialog + Toast/Alert (11+ row) | Escape/dismiss semantics and focus behavior verified per overlay | | E+ — motion | Unit 14 (§6): per-component passes, each a few lines on gpui's own primitives — forwarding, Switch thumb, Toast slide, Dialog scrim, Progress cap | ✅ Landed (2026-10-30), each pass reviewed in the gallery; reduce-motion snaps gpui-side with the flag forwarded | | F — platforms + data | ✅ Landed (2026-10-30): `crates/data` (List, Tree, Tabs, Sidebar), the Linux theme sources (Omarchy/KDE/GNOME), the Windows source, the Segoe Fluent + freedesktop icon resolvers, plus per-platform elevation stacks (D24, 2026-11-01) | Remaining acceptance: side-by-side gallery screenshots — a Linux run (Omarchy/KDE/GNOME) and the first Windows hardware build | -| G — migration additions (§6.3) | Waves M1–M2: Badge, Select, Segmented, Context menu, Empty state, password + multiline input, Avatar image, then M9+ → 0.1 | Each unit through the review gate; each wave unblocks a real signed screen | -| H — layout crate (§6.4) | ResizeHandle, Shell bootstrap (left sidebar on, right sidebar off, center dock) + the client-side window frame + titlebar and window controls, Dock with drag-to-split, panel-declared sidebar auto-open | Each unit through the review gate; the gallery workspace page doubles as the bootstrap example apps copy | +| G — migration additions (§6.3) | M1 ✅ (2026-11-01): Badge, Select, Segmented, Context menu, Empty state, password + multiline input. M8 ✅ (2026-11-03): Avatar image. Next: Wave M2 (M9–M14, M18) → 0.1 | Each unit through the review gate; each wave unblocks a real signed screen | +| H — layout crate (§6.4) ✅ Landed (2026-11-01 → 11-03) | ResizeHandle, Shell bootstrap + client-side window frame, Dock with drag-to-split, titlebar + window controls (app-owned dragging), panel-declared sidebar auto-open | Each unit through the review gate; the gallery is the bootstrap example — the shell with the catalog sidebar and a dock of component panels | Gate for every phase: `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test`, gallery smoke run. @@ -363,20 +364,18 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio | D23 | The old Toast split into `Alert` + `Toast` (2026-10-31): one component can't be both a persistent page callout and a fleeting bottom-center nudge — different lifetime (persists until acted on vs auto-dismisses), different placement (inline in flow vs floating bottom center), different content shape (title + message + severity tint vs one line + icon + action). Alert is fully controlled — the app renders it while the condition holds and its close button reports intent; Toast keeps the store + timers. Toast drops severity and titles: Adwaita toasts are single-line nudges, and severity belongs to the banners that stay on the page | | D24 | Shadows follow the platform's design language (2026-11-01): in-app shadows are painted by gpui, so "native" means each platform's shadow *values* — `Elevation` is a two-layer key + ambient stack and `resolve` picks it per `SourceKind` (macOS soft and layered at 12% alpha, Windows tight with more offset, Linux compact and darker; one shared dark stack where borders do the separation work). Static design constants, never OS-read (no platform API states shadow values); replaces the Alert's light-mode alpha hack, which was a single-profile workaround | | D25 | Components fetch, never scale (2026-11-01): every derived metric a component kept re-deriving from root metrics — dense label text (`×0.8/0.85`), dense inline icons (`×0.75`), dense horizontal padding (`×0.5/0.75/2`), plate insets (`0.125/0.25/0.375 rem`), strip band heights (`×1/1.15/1.35`) — moved into the per-size-class `ControlMetrics` table (`caption_size`, `dense_icon_size`, `dense_padding_x`, `plate_inset`, `strip_height`, plus `indicator_size` for checkboxes, radios, and switch knobs), computed once in `with_root_font_size`. The table stays role-based — one value per concept per size class, shared by every component — not gpuikit's per-component token layers (D22). Component-unique geometry that has no second consumer keeps its ratios over root metrics (switch track length, slider knob, avatar initials, tree indent). Two icon roles are fetch-only: content icons (alert info, segmented options) take `icon_size` — never smaller than the label — and indicator glyphs (checks, chevrons, close buttons) take `dense_icon_size` | -| D26 | Application layout is a crate, not app-side code (2026-11-01): `crates/layout` bootstraps the shell — left sidebar (on by default), right sidebar (off), and a center dock whose panes drag-to-split — reversing the old "dock stays app-side" deferral. Prompted by signed's `crates/dock` (a gpui-component `DockArea` behind a custom skin): it proves the demand and marks the complexity we skip — no renderer/skin seam (one theme-baked appearance), no serialization, no zoom/collapse modes, no floating windows, no JS host. The API stays smaller than gpui-kit's dock and zed's editor dock (both D15/D20 references, read only): an application-owned `LayoutState`, a controlled pane tree, registered panel descriptors, and a handful of ops (split / close / activate / toggle sidebar); a panel declares `needs_right_sidebar` and the layout opens it on activation | +| D26 | Application layout is a crate, not app-side code (2026-11-01): `crates/layout` bootstraps the shell — left sidebar (on by default), right sidebar (off), and a center dock whose panes drag-to-split — reversing the old "dock stays app-side" deferral. Prompted by signed's `crates/dock` (a gpui-component `DockArea` behind a custom skin): it proves the demand and marks the complexity we skip — no renderer/skin seam (one theme-baked appearance), no serialization, no zoom/collapse modes, no floating windows, no JS host. The API stays smaller than gpui-kit's dock and zed's editor dock (both D15/D20 references, read only): an application-owned `LayoutState`, a controlled pane tree, registered panel descriptors, and a handful of ops (split / close / activate / toggle sidebar); a panel declares a right-sidebar view and the dock asks the app to open the sidebar on activation | Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in a lighter form as D20. D10 (no icon bundling) is superseded by D19. -## 12. Immediate next steps +## 12. Next steps -1. ~~Phase A scaffold~~ ✅ — workspace, toolchain pin, CI, theme seed, icons crate, core feature scaffolding, facade, gallery shell. -2. ~~Phase B theme engine core~~ ✅ — resolve pipeline, OKLCH ramps, presets, macOS source + watchers, gallery theme inspector. -3. ~~Phase C controls~~ ✅ — units 1–9 (Button → Avatar), each through the review gate; sizing API (§6.1). -4. ~~Phase D input~~ ✅ — single-line Input (unit 10) + the §6.2 completion pass (modifiers, undo/redo, read-only, actions + key context, caret blink, paint polish). NumberInput stays deferred; multiline returns as §6.3 M7. -5. ~~Phase E overlays~~ ✅ — Popover (unit 11), ScrollArea (unit 11.5), Menu (unit 12), Tooltip + Dialog/AlertDialog + Toast (unit 11+ row; Sheet dropped). Phase gate held: escape/dismiss semantics and focus behavior per overlay. -6. ~~Unit 14 — motion, per component (§6 row 14, D21)~~ ✅ Landed in five passes (2026-10-30): (a) theme `reduce_motion` → `cx.set_reduce_motion` in `reapply`; (b) Switch thumb `with_spring`; (c) Toast 8 px slide-in; (d) Dialog scrim enters with the panel; (e) Progress 30 fps cap. Exits and hover washes stay deferred (§6 deferred list). -7. ~~Phase F, data first~~ ✅ data complete (2026-10-30) — `crates/data` opened with List, then Tree, Tabs, Sidebar (the dead `sheet` feature dropped from core along the way; Table dropped from the roadmap). Also extracted `crates/utils` (2026-10-30): `h_flex`/`v_flex` (core's `common` re-exports them), `focus_handle` (the namespaced keyed handle, 8 call sites), `focus_ring` (7 sites), `adjacent_selectable` + `nav_key` (4 sites), and the selectable-row paint helpers `selectable_label_color`/`paint_selectable_row` (3 sites, reading `&Theme` directly — no intermediate color struct), plus the `StyledExt` trait (`refine_style` — adapted from gpui-kit `base`, D15 — replacing the per-component `style().refine` pattern; 5 sites) — `core` and `data` depend on it; the facade doesn't re-export it. **Linux theme sources landed (2026-10-30)**: Omarchy (colors.toml parse + state-dir detection + inotify on `current`), KDE (kdeglobals `[Colors:*]` + AccentColor + `~/.config` inotify), GNOME/generic (portal `ReadAll` + `SettingChanged` via zbus 5 blocking on its own thread, gsettings fallback with the libadwaita accent table) — `SourceKind::Omarchy/Kde/Gnome`, detection cascade Omarchy → KDE → portal → fallback, all parsers fixture-tested on every OS and the Linux build compile-checked via `cargo check --target x86_64-unknown-linux-gnu`. **Windows source + icon resolvers landed (2026-10-30)**: `SourceKind::Windows` (WinRT `UISettings` + `Personalize` fallback + `ColorValuesChanged`/`HighContrastChanged`, §5.3), the Segoe Fluent codepoint renderer, and the freedesktop theme walker (§4.4); the WinRT surface was verified against the real `windows` 0.62 bindings via a standalone cross-check (gpui's own manifest build needs llvm-rc from macOS). Remaining Phase F: **side-by-side gallery screenshots per platform** — Omarchy/KDE/GNOME need a Linux run, Windows needs its first hardware build. NumberInput stays deferred until needed; multiline landed as §6.3 M7 (2026-11-01). -8. Then — Phase G is the migration additions (§6.3): Badge ✅, Select ✅, Segmented control ✅, Context menu ✅, Empty state ✅, password input ✅, multiline textarea ✅, then Avatar image (M8) — each unblocks a concrete signed screen. Pickers (Calendar/DatePicker, TimeField, ColorPicker) stay decided-at-unit-time. `crates/editor` starts when file viewing (M15) is reached. -9. Then — the layout crate (§6.4): ResizeHandle ✅, Shell ✅, window frame ✅, Dock ✅, drag-to-split ✅, titlebar + window controls ✅, sidebar auto-open ✅ — §6.4 is complete; further layout work (persistence, pane zoom, bottom dock) waits for a real consumer. Order against M9+ follows signed's needs. +Complete, in landing order: Phase A scaffold · B theme engine (resolve pipeline, OKLCH ramps, presets, macOS source + watchers, gallery inspector) · C controls, units 1–9 + the sizing API (§6.1) · D input (§6.2) · E overlays · E+ motion (D21) · F platforms + data (`crates/data`; `crates/utils` extracted; Linux theme sources; Windows source; Segoe Fluent + freedesktop icon resolvers; per-platform elevation stacks) · G waves M1 + M8 (Badge, Select, Segmented, Context menu, Empty state, password input, multiline textarea, Avatar image) · H the layout crate end to end (§6.4: ResizeHandle, Shell, window frame, Dock, drag-to-split, titlebar + window controls, sidebar auto-open). Landed notes live in §4–§7; nothing below is started. -Batched polish candidates between units: scrollbar auto-hide + horizontal thumb, `Undo`/`Redo` menu events. +Next, in order: + +1. **Wave M2** (§6.3): Field wrapper (M9, the settings-dialog label/description/error wrapper), Tag (M10), Status dot (M11), Combobox (M12), Command palette (M13), VirtualList (M14). The Spinner (M18) lands with its next consumer — before signed wires repo loading into dock tabs (`Tab::busy` holds a placeholder ring today). Each unit through the review gate; order follows signed's needs. +2. **Wave M3 opens `crates/editor`** (D12): code view (M15), diff view (M16), Markdown view (M17) when a consumer confirms. +3. **Phase F tail:** side-by-side gallery screenshots — a Linux run (Omarchy/KDE/GNOME) and the first Windows hardware build. + +Pickers (Calendar/DatePicker, TimeField, ColorPicker) stay decided-at-unit-time. Deferred layout work (persistence, pane zoom, bottom dock, collapse-to-edge, floating windows) waits for a real consumer. Batched polish candidates between units: scrollbar auto-hide + horizontal thumb, `Undo`/`Redo` menu events. diff --git a/examples/gallery/Cargo.toml b/examples/gallery/Cargo.toml index 13f78fc..b07091b 100644 --- a/examples/gallery/Cargo.toml +++ b/examples/gallery/Cargo.toml @@ -7,6 +7,7 @@ description = "Live preview of every component against the resolved system theme [dependencies] gpui = { workspace = true } gpui_platform = { workspace = true } +reqwest_client = { workspace = true } gpui-native-core = { workspace = true, features = ["full"] } gpui-native-data = { workspace = true, features = ["full"] } gpui-native-layout = { workspace = true } diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 47a5a29..20a562f 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -1253,7 +1253,7 @@ impl Render for AvatarDemo { .size_full() .p_4() .child(demo_section( - "pictures fall back to initials", + "pictures, with a seeded pixel-art fallback", colors, h_flex() .flex_wrap() @@ -1263,13 +1263,18 @@ impl Render for AvatarDemo { .name("Reya Adele") .picture(Some("https://github.com/reya.png")), ) + .child(Avatar::new("ada@reya.info").name("Ada Lovelace")) .child( - Avatar::new("ada@reya.info") - .name("Ada Lovelace") - .picture(Some("https://example.com/missing.png")), + Avatar::new("grace@reya.info") + .name("Grace Hopper") + .fully_rounded(true), ) - .child(Avatar::new("grace@reya.info").name("Grace Hopper").small()) - .child(Avatar::new("desk@reya.info").name("Help Desk").large()), + .child( + Avatar::new("desk@reya.info") + .name("Help Desk") + .fully_rounded(true) + .large(), + ), )) } } @@ -2410,79 +2415,84 @@ where } fn main() { - application().with_assets(IconAssets).run(|cx: &mut App| { - gpui_native_core::init(cx); - let layout_state = LayoutState::new(cx); - let dock_state = DockState::new(cx); - // A panel that declares a right sidebar view asks for it to open on - // activation; visibility itself stays on the shell's LayoutState. - let layout_for_dock = layout_state.clone(); - dock_state.update(cx, |dock, _| { - dock.on_open_right_sidebar(move |cx| { - layout_for_dock.update(cx, |state, cx| { - state.set_right_sidebar_visible(true, cx); + // Without a client gpui installs a NullHttpClient and remote images — + // the avatar pictures — silently never load. + application() + .with_assets(IconAssets) + .with_http_client(std::sync::Arc::new(reqwest_client::ReqwestClient::new())) + .run(|cx: &mut App| { + gpui_native_core::init(cx); + let layout_state = LayoutState::new(cx); + let dock_state = DockState::new(cx); + // A panel that declares a right sidebar view asks for it to open on + // activation; visibility itself stays on the shell's LayoutState. + let layout_for_dock = layout_state.clone(); + dock_state.update(cx, |dock, _| { + dock.on_open_right_sidebar(move |cx| { + layout_for_dock.update(cx, |state, cx| { + state.set_right_sidebar_visible(true, cx); + }); }); }); - }); - let bounds = Bounds::centered(None, size(px(960.0), px(640.0)), cx); - if let Err(err) = cx.open_window( - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - titlebar: Some(gpui::TitlebarOptions { - title: Some("gpui-native-kit gallery".into()), - // The shell's strips are the titlebar; the native one only - // hosts the traffic lights on macOS. - appears_transparent: true, + let bounds = Bounds::centered(None, size(px(960.0), px(640.0)), cx); + if let Err(err) = cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + titlebar: Some(gpui::TitlebarOptions { + title: Some("gpui-native-kit gallery".into()), + // The shell's strips are the titlebar; the native one only + // hosts the traffic lights on macOS. + appears_transparent: true, + ..Default::default() + }), + // The strips move the window themselves, so a tab dragged inside + // the titlebar band never drags the window with it. + app_owns_titlebar_drag: true, ..Default::default() - }), - // The strips move the window themselves, so a tab dragged inside - // the titlebar band never drags the window with it. - app_owns_titlebar_drag: true, - ..Default::default() - }, - |window, cx| { - Theme::sync_from_window(window, cx); - Theme::sync_window_rem_size(window, cx); - // Detached on purpose: the subscription lives as long as the window. - window - .observe_window_appearance(|window, cx| { - Theme::sync_from_window(window, cx); - Theme::sync_window_rem_size(window, cx); - cx.refresh_windows(); - }) - .detach(); - let toasts = cx.new(|_| Toasts::default()); - let alerts = cx.new(|_| Alerts::default()); - let gallery = cx.new(|cx| { - // Toast pushes and dismissals repaint the stack. - cx.observe(&toasts, |_, _, cx| cx.notify()).detach(); - // Alert pushes and dismissals repaint it. - cx.observe(&alerts, |_, _, cx| cx.notify()).detach(); - Gallery { - layout_state: layout_state.clone(), - dock_state: dock_state.clone(), - alerts: alerts.clone(), - toasts: toasts.clone(), - dialog_open: false, - alert_open: false, - } - }); - dock_state.update(cx, |dock, cx| { - Gallery::register_panels( - dock, - layout_state.clone(), - alerts.clone(), - toasts.clone(), - gallery.downgrade(), - cx, - ); - dock.open_panel("welcome", window, cx); - }); - gallery - }, - ) { - eprintln!("failed to open gallery window: {err}"); - } - cx.activate(true); - }); + }, + |window, cx| { + Theme::sync_from_window(window, cx); + Theme::sync_window_rem_size(window, cx); + // Detached on purpose: the subscription lives as long as the window. + window + .observe_window_appearance(|window, cx| { + Theme::sync_from_window(window, cx); + Theme::sync_window_rem_size(window, cx); + cx.refresh_windows(); + }) + .detach(); + let toasts = cx.new(|_| Toasts::default()); + let alerts = cx.new(|_| Alerts::default()); + let gallery = cx.new(|cx| { + // Toast pushes and dismissals repaint the stack. + cx.observe(&toasts, |_, _, cx| cx.notify()).detach(); + // Alert pushes and dismissals repaint it. + cx.observe(&alerts, |_, _, cx| cx.notify()).detach(); + Gallery { + layout_state: layout_state.clone(), + dock_state: dock_state.clone(), + alerts: alerts.clone(), + toasts: toasts.clone(), + dialog_open: false, + alert_open: false, + } + }); + dock_state.update(cx, |dock, cx| { + Gallery::register_panels( + dock, + layout_state.clone(), + alerts.clone(), + toasts.clone(), + gallery.downgrade(), + cx, + ); + dock.open_panel("welcome", window, cx); + }); + gallery + }, + ) { + eprintln!("failed to open gallery window: {err}"); + } + cx.activate(true); + }); }