diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,21 +4,21 @@ version = 4
 
 [[package]]
 name = "accesskit"
-version = "0.24.1"
+version = "0.25.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320"
+checksum = "ad442f58ee04714aaa0ba0a2768c1ea1935b29507bb22ddece8cbbc76db02932"
 dependencies = [
  "uuid",
 ]
 
 [[package]]
 name = "accesskit_consumer"
-version = "0.35.0"
+version = "0.39.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10"
+checksum = "8a45e2bb300bea6d5f33a82c3e051c3ddc6f41c209750f68a0acbe991a284344"
 dependencies = [
  "accesskit",
- "hashbrown 0.16.1",
+ "hashbrown 0.17.1",
 ]
 
 [[package]]
@@ -94,26 +94,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "arboard"
-version = "3.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
-dependencies = [
- "clipboard-win",
- "image",
- "log",
- "objc2 0.6.5",
- "objc2-app-kit 0.3.2",
- "objc2-core-foundation",
- "objc2-core-graphics",
- "objc2-foundation 0.3.2",
- "parking_lot",
- "percent-encoding",
- "windows-sys 0.60.2",
- "x11rb",
-]
-
 [[package]]
 name = "arrayref"
 version = "0.3.9"
@@ -327,30 +307,6 @@ dependencies = [
  "thiserror 1.0.69",
 ]
 
-[[package]]
-name = "calloop"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
-dependencies = [
- "bitflags 2.13.2",
- "polling",
- "rustix 1.1.5",
- "tracing",
-]
-
-[[package]]
-name = "calloop-wayland-source"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
-dependencies = [
- "calloop 0.14.5",
- "rustix 1.1.5",
- "wayland-backend",
- "wayland-client",
-]
-
 [[package]]
 name = "cast"
 version = "0.3.0"
@@ -406,15 +362,6 @@ dependencies = [
  "windows-link",
 ]
 
-[[package]]
-name = "clipboard-win"
-version = "5.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
-dependencies = [
- "error-code",
-]
-
 [[package]]
 name = "cmake"
 version = "0.1.58"
@@ -862,12 +809,6 @@ dependencies = [
  "litrs",
 ]
 
-[[package]]
-name = "downcast-rs"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
-
 [[package]]
 name = "dpi"
 version = "0.1.2"
@@ -882,9 +823,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
 
 [[package]]
 name = "ecolor"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34"
+checksum = "31bcbef0d4263a63eb32c4834e5d711613035ce1333468e107d928923e21e6e2"
 dependencies = [
  "bytemuck",
  "emath",
@@ -892,9 +833,9 @@ dependencies = [
 
 [[package]]
 name = "eframe"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2"
+checksum = "9c5642501a65ca657d05f00c643a5c14ff2346cb3818032bfe23d58e1f8e5f84"
 dependencies = [
  "ahash",
  "bytemuck",
@@ -913,6 +854,7 @@ dependencies = [
  "pollster",
  "profiling",
  "raw-window-handle",
+ "send_wrapper",
  "static_assertions",
  "wasm-bindgen",
  "web-sys",
@@ -924,9 +866,9 @@ dependencies = [
 
 [[package]]
 name = "egui"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba"
+checksum = "bec9144f49b7a28b5f395d3147e325b4fe3e77ba686eac11d1bdf0a7da3f375f"
 dependencies = [
  "accesskit",
  "ahash",
@@ -944,9 +886,9 @@ dependencies = [
 
 [[package]]
 name = "egui-wgpu"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cef1f6d1bba40b09a4d0d62cb822b81030922cfbf65c5373398c3a3caafc5bd"
+checksum = "0309cbe1febed48af5b974e4bcc424b35e9664863a4d402697147b57a488c864"
 dependencies = [
  "ahash",
  "bytemuck",
@@ -964,29 +906,29 @@ dependencies = [
 
 [[package]]
 name = "egui-winit"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc"
+checksum = "e4e449f4c4a2a60c7ccb4ba7fa4078417bfe1f9ddc03b5e05ce75748e33a70de"
 dependencies = [
- "arboard",
- "bytemuck",
+ "block2 0.6.2",
  "egui",
  "log",
  "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
  "objc2-foundation 0.3.2",
  "objc2-ui-kit 0.3.2",
  "profiling",
  "raw-window-handle",
- "smithay-clipboard",
  "web-time",
+ "windows-sys 0.61.2",
  "winit",
 ]
 
 [[package]]
 name = "egui_extras"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05cbdc785020861553f230f23d330ba1fb72613b9d8e0e457f3bdc8ba6db6dcf"
+checksum = "def286cf47194f3fe45b8e37c503ada7a20a64b59a903b4bc704baec943b1827"
 dependencies = [
  "ahash",
  "egui",
@@ -999,9 +941,9 @@ dependencies = [
 
 [[package]]
 name = "egui_kittest"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eb3f3effb2726fee23449293e67acbf6cc6ce35525b1722bd5743bb348114d5"
+checksum = "0979c2e37860fcf13a2bea77508607f067a9a6a84ba9bd47b5e298582fb59b86"
 dependencies = [
  "eframe",
  "egui",
@@ -1023,9 +965,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
 
 [[package]]
 name = "emath"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1"
+checksum = "e73ce90f54750be8672c02be0743fe47530e6f251e066f0ee2869b59eb297af5"
 dependencies = [
  "bytemuck",
 ]
@@ -1046,29 +988,29 @@ dependencies = [
 
 [[package]]
 name = "enum-map"
-version = "2.7.3"
+version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
+checksum = "d4532960034c15ba2f55e9eee631040ecc473c32e6c4b200828c759840f13a06"
 dependencies = [
  "enum-map-derive",
 ]
 
 [[package]]
 name = "enum-map-derive"
-version = "0.17.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
+checksum = "7127272f64b3e098f7d81f350d672081b879bff896d219c42370dc858b1af9ff"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.6",
 ]
 
 [[package]]
 name = "epaint"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3"
+checksum = "97bac86ae3dde93fc3951e790951caea739ff39a6c89d909cdaf1d9278dbb1bd"
 dependencies = [
  "ahash",
  "bytemuck",
@@ -1085,15 +1027,16 @@ dependencies = [
  "skrifa 0.44.0",
  "smallvec",
  "unicode-general-category",
+ "unicode-properties",
  "unicode-segmentation",
- "vello_cpu 0.1.0",
+ "vello_cpu 0.2.0",
 ]
 
 [[package]]
 name = "epaint_default_fonts"
-version = "0.36.2"
+version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13"
+checksum = "cfc1cc505827f4c23eedc297975a0fd038268d41293f389dd8ff3b574aefab19"
 
 [[package]]
 name = "equivalent"
@@ -1111,12 +1054,6 @@ dependencies = [
  "windows-sys 0.61.2",
 ]
 
-[[package]]
-name = "error-code"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
-
 [[package]]
 name = "euclid"
 version = "0.22.14"
@@ -1132,12 +1069,6 @@ version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
 
-[[package]]
-name = "fax"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
-
 [[package]]
 name = "fdeflate"
 version = "0.3.7"
@@ -1348,16 +1279,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "gethostname"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
-dependencies = [
- "rustix 1.1.5",
- "windows-link",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.2.17"
@@ -1425,9 +1346,9 @@ dependencies = [
 
 [[package]]
 name = "glifo"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a"
+checksum = "282a26c1e23de04bdab3e34a21b6f877a479b96737792516b9b8b8f69b6661be"
 dependencies = [
  "bytemuck",
  "foldhash",
@@ -1436,7 +1357,7 @@ dependencies = [
  "peniko",
  "skrifa 0.44.0",
  "smallvec",
- "vello_common 0.1.0",
+ "vello_common 0.2.0",
 ]
 
 [[package]]
@@ -1899,7 +1820,6 @@ dependencies = [
  "moxcms",
  "num-traits",
  "png 0.18.1",
- "tiff",
 ]
 
 [[package]]
@@ -2063,9 +1983,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
 
 [[package]]
 name = "kittest"
-version = "0.4.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ceaa75eb0036a32b6b9833962eb18137449e9817e2e586006471925b727fd5"
+checksum = "0bb81a721ba76aa095b7f45deb180504f786904e56ca4b8e141f66eb89a78b75"
 dependencies = [
  "accesskit",
  "accesskit_consumer",
@@ -2212,15 +2132,6 @@ version = "2.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
 
-[[package]]
-name = "memmap2"
-version = "0.9.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "mime"
 version = "0.3.17"
@@ -2465,9 +2376,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
 dependencies = [
  "bitflags 2.13.2",
+ "block2 0.6.2",
  "objc2 0.6.5",
  "objc2-core-foundation",
- "objc2-core-graphics",
  "objc2-foundation 0.3.2",
 ]
 
@@ -2524,7 +2435,6 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
 dependencies = [
- "bitflags 2.13.2",
  "dispatch2",
  "objc2 0.6.5",
  "objc2-core-foundation",
@@ -3084,15 +2994,6 @@ version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
 
-[[package]]
-name = "quick-xml"
-version = "0.41.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
-dependencies = [
- "memchr",
-]
-
 [[package]]
 name = "quinn"
 version = "0.11.12"
@@ -3578,12 +3479,6 @@ dependencies = [
  "windows-sys 0.61.2",
 ]
 
-[[package]]
-name = "scoped-tls"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
-
 [[package]]
 name = "scopeguard"
 version = "1.2.0"
@@ -3625,6 +3520,12 @@ version = "1.0.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
 
+[[package]]
+name = "send_wrapper"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+
 [[package]]
 name = "serde"
 version = "1.0.229"
@@ -3985,44 +3886,6 @@ version = "1.16.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
 
-[[package]]
-name = "smithay-client-toolkit"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
-dependencies = [
- "bitflags 2.13.2",
- "calloop 0.14.5",
- "calloop-wayland-source",
- "cursor-icon",
- "libc",
- "log",
- "memmap2",
- "rustix 1.1.5",
- "thiserror 2.0.21",
- "wayland-backend",
- "wayland-client",
- "wayland-csd-frame",
- "wayland-cursor",
- "wayland-protocols",
- "wayland-protocols-experimental",
- "wayland-protocols-misc",
- "wayland-protocols-wlr",
- "wayland-scanner",
- "xkeysym",
-]
-
-[[package]]
-name = "smithay-clipboard"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
-dependencies = [
- "libc",
- "smithay-client-toolkit",
- "wayland-backend",
-]
-
 [[package]]
 name = "smol_str"
 version = "0.2.2"
@@ -4288,20 +4151,6 @@ dependencies = [
  "syn 3.0.6",
 ]
 
-[[package]]
-name = "tiff"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
-dependencies = [
- "fax",
- "flate2",
- "half",
- "quick-error",
- "weezl",
- "zune-jpeg",
-]
-
 [[package]]
 name = "tiny-skia"
 version = "0.11.4"
@@ -4517,7 +4366,6 @@ version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
 dependencies = [
- "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -4730,9 +4578,9 @@ dependencies = [
 
 [[package]]
 name = "vello_common"
-version = "0.1.0"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2"
+checksum = "bbb2141a2bca6e6d598e471fd4d1d7eed8e020aad6a28187edda07f091a325dd"
 dependencies = [
  "bytemuck",
  "fearless_simd",
@@ -4758,14 +4606,14 @@ dependencies = [
 
 [[package]]
 name = "vello_cpu"
-version = "0.1.0"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea"
+checksum = "ace506ca414548966fcf3c283be94eb1cad8254de488791ee698bf438b0890a9"
 dependencies = [
  "bytemuck",
- "glifo 0.2.0",
+ "glifo 0.3.0",
  "hashbrown 0.17.1",
- "vello_common 0.1.0",
+ "vello_common 0.2.0",
 ]
 
 [[package]]
@@ -4894,116 +4742,6 @@ version = "0.2.129"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4f692aa943ccd88363733b77063f32cfed5bc6cbea8e6e8b251b302f881606fe"
 
-[[package]]
-name = "wayland-backend"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
-dependencies = [
- "cc",
- "downcast-rs",
- "rustix 1.1.5",
- "scoped-tls",
- "smallvec",
- "wayland-sys",
-]
-
-[[package]]
-name = "wayland-client"
-version = "0.31.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
-dependencies = [
- "bitflags 2.13.2",
- "rustix 1.1.5",
- "wayland-backend",
- "wayland-scanner",
-]
-
-[[package]]
-name = "wayland-csd-frame"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
-dependencies = [
- "bitflags 2.13.2",
- "cursor-icon",
- "wayland-backend",
-]
-
-[[package]]
-name = "wayland-cursor"
-version = "0.31.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d"
-dependencies = [
- "rustix 1.1.5",
- "wayland-client",
- "xcursor",
-]
-
-[[package]]
-name = "wayland-protocols"
-version = "0.32.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
-dependencies = [
- "bitflags 2.13.2",
- "wayland-backend",
- "wayland-client",
- "wayland-scanner",
-]
-
-[[package]]
-name = "wayland-protocols-experimental"
-version = "20250721.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
-dependencies = [
- "bitflags 2.13.2",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
-[[package]]
-name = "wayland-protocols-misc"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
-dependencies = [
- "bitflags 2.13.2",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
-[[package]]
-name = "wayland-protocols-wlr"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
-dependencies = [
- "bitflags 2.13.2",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
-[[package]]
-name = "wayland-scanner"
-version = "0.31.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
-dependencies = [
- "proc-macro2",
- "quick-xml",
- "quote",
-]
-
 [[package]]
 name = "wayland-sys"
 version = "0.31.11"
@@ -5361,7 +5099,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
@@ -5370,16 +5108,7 @@ version = "0.59.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
 dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
-dependencies = [
- "windows-targets 0.53.5",
+ "windows-targets",
 ]
 
 [[package]]
@@ -5397,31 +5126,14 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "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",
- "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",
+ "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",
 ]
 
 [[package]]
@@ -5439,96 +5151,48 @@ 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 = "winit"
 version = "0.30.13"
@@ -5539,7 +5203,7 @@ dependencies = [
  "atomic-waker",
  "bitflags 2.13.2",
  "block2 0.5.1",
- "calloop 0.13.0",
+ "calloop",
  "cfg_aliases",
  "concurrent-queue",
  "core-foundation 0.9.4",
@@ -5624,23 +5288,6 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
 
-[[package]]
-name = "x11rb"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
-dependencies = [
- "gethostname",
- "rustix 1.1.5",
- "x11rb-protocol",
-]
-
-[[package]]
-name = "x11rb-protocol"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
-
 [[package]]
 name = "x509-cert"
 version = "0.2.5"
@@ -5676,12 +5323,6 @@ dependencies = [
  "rustix 1.1.5",
 ]
 
-[[package]]
-name = "xcursor"
-version = "0.3.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
-
 [[package]]
 name = "xkbcommon-dl"
 version = "0.4.2"
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,7 @@ craft-print = { path = "crates/print" }
 craft-quit = { path = "crates/quit" }
 craft-settings = { path = "crates/settings" }
 craft-ui = { path = "crates/ui" }
-egui = { version = "0.36", default-features = false }
+egui = { version = "0.37", default-features = false }
 rayon = "1"
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml
--- a/crates/ui/Cargo.toml
+++ b/crates/ui/Cargo.toml
@@ -20,6 +20,6 @@ serde = ["dep:serde"]
 [dev-dependencies]
 egui = { workspace = true, features = ["default_fonts"] }
 serde_json = "1"
-egui_extras = { version = "0.36", default-features = false, features = ["svg"] }
-egui_kittest = { version = "0.36", features = ["wgpu"] }
+egui_extras = { version = "0.37", default-features = false, features = ["svg"] }
+egui_kittest = { version = "0.37", features = ["wgpu"] }
 image = { version = "0.25", default-features = false, features = ["png"] }
diff --git a/crates/ui/src/buttons/mod.rs b/crates/ui/src/buttons/mod.rs
--- a/crates/ui/src/buttons/mod.rs
+++ b/crates/ui/src/buttons/mod.rs
@@ -81,7 +81,7 @@ impl<'a> IconButton<'a> {
         let valid = self.size.is_finite() && self.size.x > 0.0 && self.size.y > 0.0 && self.size.x <= 4096.0 && self.size.y <= 4096.0;
         let (rect, response) =
             ui.allocate_exact_size(if valid { self.size } else { Vec2::ZERO }, if valid { Sense::click() } else { Sense::hover() });
-        response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Button, valid && ui.is_enabled(), self.selected, self.label));
+        response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, valid && ui.is_enabled(), self.selected, self.label));
         if !valid {
             return response;
         }
diff --git a/crates/ui/src/docking/surface.rs b/crates/ui/src/docking/surface.rs
--- a/crates/ui/src/docking/surface.rs
+++ b/crates/ui/src/docking/surface.rs
@@ -213,14 +213,14 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> DockArea<P> {
         while let Some((node, path)) = nodes.pop() {
             match node {
                 Node::Tabs { panels, .. } if group(panels) => return true,
-                Node::Stack { entries } => {
+                Node::Stack { entries }
                     if entries.iter().any(|entry| {
                         matches(self.id.with(("tab", &entry.panel)))
                             || matches(self.id.with(("collapse", &entry.panel)))
                             || matches(self.id.with(("stack-resize", &entry.panel)))
-                    }) {
-                        return true;
-                    }
+                    }) =>
+                {
+                    return true;
                 }
                 Node::Split { first, second, .. } => {
                     if matches(self.id.with(("split", &path))) {
@@ -416,9 +416,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
                     self.tab(ui, &entry.panel, title_rect, entry.open, panel_rect);
                     let collapse = Rect::from_min_max(pos2((title_rect.right() - 18.0).max(title_rect.left()), title_rect.top()), title_rect.max);
                     let response = ui.interact(collapse, self.id.with(("collapse", &entry.panel)), Sense::click());
-                    response.widget_info(|| {
-                        egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), entry.open, (self.label)(&entry.panel))
-                    });
+                    response
+                        .widget_info(|| egui::WidgetInfo::selected(egui::Role::CheckBox, ui.is_enabled(), entry.open, (self.label)(&entry.panel)));
                     ui.painter().with_clip_rect(collapse).text(
                         collapse.center(),
                         egui::Align2::CENTER_CENTER,
@@ -536,7 +535,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
         }
         ui.painter().rect_filled(rect, 0.0, self.style.background);
         let mut child =
-            ui.new_child(UiBuilder::new().id(self.id.with(("body", panel))).max_rect(rect).layout(egui::Layout::top_down(egui::Align::Min)));
+            ui.new_child(UiBuilder::new().scope_id(self.id.with(("body", panel))).max_rect(rect).layout(egui::Layout::top_down(egui::Align::Min)));
         child.set_clip_rect(rect.intersect(ui.clip_rect()));
         self.content.body(&mut child, panel);
         if child.rect_contains_pointer(rect) && child.input(|i| i.pointer.primary_pressed()) {
@@ -566,7 +565,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
             let grip = Rect::from_min_max(strip.min, pos2((strip.left() + 12.0).min(strip.right()), strip.bottom()));
             let response = ui.interact(grip, self.id.with(("move-group", panels.first())), Sense::drag()).on_hover_cursor(egui::CursorIcon::Grab);
             self.register_interaction(ui.ctx(), response.id);
-            response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.move_label));
+            response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.move_label));
             if panels.iter().all(|p| (self.permissions)(p).movable)
                 && response.drag_started()
                 && let Some(first) = panels.first()
@@ -597,7 +596,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
         let header_width = if requested.is_finite() { requested.clamp(0.0, strip.width()) } else { 0.0 };
         if header_width > 0.0 {
             let header = Rect::from_min_max(pos2(strip.right() - header_width, strip.top()), strip.max);
-            let mut child = ui.new_child(UiBuilder::new().id(self.id.with(("header", selected))).max_rect(header));
+            let mut child = ui.new_child(UiBuilder::new().scope_id(self.id.with(("header", selected))).max_rect(header));
             child.set_clip_rect(header.intersect(ui.clip_rect()));
             self.content.group_header(&mut child, panels, selected, floating);
         }
@@ -625,7 +624,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
         if !fit.overflow.is_empty() && fit.overflow_width > 0.0 {
             let overflow = Rect::from_min_size(pos2(x, strip.top()), vec2(fit.overflow_width, strip.height()));
             let response = ui.interact(overflow, self.id.with(("overflow", selected)), Sense::click());
-            response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.panels_label));
+            response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.panels_label));
             ui.painter().with_clip_rect(overflow).text(overflow.center(), egui::Align2::CENTER_CENTER, "…", self.style.font.clone(), self.style.text);
             egui::Popup::menu(&response).show(|ui| {
                 egui::ScrollArea::vertical().max_height(360.0).show(ui, |ui| {
@@ -663,7 +662,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
         let logical = (self.label)(panel);
         let text = self.content.label(ui, panel, &logical, &self.style.font, self.style.text);
         let response = ui.button(text);
-        response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &logical));
+        response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &logical));
         response
     }
 
@@ -698,7 +697,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
             if policy.movable && policy.floatable {
                 let text = self.content.action_label(ui, &self.style.float_label, &self.style.font, self.style.text);
                 let response = ui.button(text);
-                response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.float_label));
+                response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.float_label));
                 if response.clicked() {
                     let rect = clamp_float(group.translate(vec2(24.0, 24.0)), ui.ctx().content_rect());
                     self.output.actions.push(Action::Float { panel: panel.clone(), rect: [rect.left(), rect.top(), rect.width(), rect.height()] });
@@ -721,12 +720,12 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
                         }
                     });
                 });
-                response.response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.move_label));
+                response.response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.move_label));
             }
             if policy.closable {
                 let text = self.content.action_label(ui, &self.style.close_label, &self.style.font, self.style.text);
                 let response = ui.button(text);
-                response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.close_label));
+                response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.close_label));
                 if response.clicked() {
                     self.output.actions.push(Action::Close { panel: panel.clone() });
                     ui.close();
@@ -766,7 +765,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
                 let corner = Rect::from_min_max((rect.max - vec2(12.0, 12.0)).max(rect.min), rect.max);
                 let response = ui.interact(corner, id.with("resize"), Sense::drag()).on_hover_cursor(egui::CursorIcon::ResizeNwSe);
                 self.register_interaction(ui.ctx(), response.id);
-                response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), &self.style.resize_window_label));
+                response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), &self.style.resize_window_label));
                 if response.drag_started() {
                     response.request_focus();
                 }
diff --git a/crates/ui/src/docking/tests.rs b/crates/ui/src/docking/tests.rs
--- a/crates/ui/src/docking/tests.rs
+++ b/crates/ui/src/docking/tests.rs
@@ -305,14 +305,14 @@ fn frame(ctx: &Context, layout: &Layout<u32>, events: Vec<Event>, focused: bool)
     let input = RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), events, focused, ..Default::default() };
     ctx.run_ui(input, |ui| {
         let style = DockStyle::from_ui(ui);
-        output = DockArea::new(Id::new("test-area")).show(
+        output = DockArea::new(Id::unique("test-area")).show(
             ui,
             layout,
             &style,
             |p| format!("Panel {p}"),
             |_| Permissions::default(),
             |ui, _| {
-                ids.push(ui.id());
+                ids.push(ui.scope_id());
                 ui.label("Actual panel content");
             },
         );
@@ -341,7 +341,7 @@ fn cancelling_or_losing_focus_during_a_drag_does_not_move_panels() {
     let source = Rect::from_min_size(pos2(0.0, 0.0), vec2(200.0, 200.0));
     let press = Event::PointerButton { pos: pos2(30.0, 30.0), button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE };
     frame(&ctx, &layout, vec![Event::PointerMoved(pos2(30.0, 30.0)), press], true);
-    assert!(begin_drag(&ctx, Id::new("test-area"), 2u32, source));
+    assert!(begin_drag(&ctx, Id::unique("test-area"), 2u32, source));
     let (out, _) = frame(
         &ctx,
         &layout,
@@ -349,7 +349,7 @@ fn cancelling_or_losing_focus_during_a_drag_does_not_move_panels() {
         true,
     );
     assert!(out.actions.is_empty());
-    assert!(begin_drag(&ctx, Id::new("test-area"), 2u32, source));
+    assert!(begin_drag(&ctx, Id::unique("test-area"), 2u32, source));
     let (out, _) = frame(&ctx, &layout, Vec::new(), false);
     assert!(out.actions.is_empty());
     let (out, _) = frame(
@@ -381,8 +381,8 @@ fn actual_pointer_drag_docks_a_floating_panel_into_the_main_tab_strip() {
     for _ in 0..3 {
         step(&ctx, &mut layout, Vec::new());
     }
-    let source = ctx.read_response(Id::new("test-area").with(("tab", &5u32))).unwrap().rect.center();
-    let target = ctx.read_response(Id::new("test-area").with(("tab", &3u32))).unwrap().rect.center();
+    let source = ctx.read_response(Id::unique("test-area").with(("tab", &5u32))).unwrap().rect.center();
+    let target = ctx.read_response(Id::unique("test-area").with(("tab", &3u32))).unwrap().rect.center();
     step(&ctx, &mut layout, vec![Event::PointerMoved(source), pointer_button(source, true)]);
     step(&ctx, &mut layout, vec![Event::PointerMoved(source + vec2(8.0, 0.0))]);
     step(&ctx, &mut layout, vec![Event::PointerMoved(target)]);
@@ -418,7 +418,7 @@ fn panel_size_limits_clamp_geometry_without_overwriting_preferred_widths() {
         let mut rects = std::collections::BTreeMap::new();
         let mut out = DockOutput::default();
         ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(width, 600.0))), ..Default::default() }, |ui| {
-            out = DockArea::new(Id::new("bounds")).show_with_limits(
+            out = DockArea::new(Id::unique("bounds")).show_with_limits(
                 ui,
                 &layout,
                 &DockStyle::from_ui(ui),
@@ -460,7 +460,7 @@ fn focused_floating_resize_grip_accepts_both_arrow_axes_and_releases_tab() {
     for _ in 0..3 {
         step(&ctx, &mut layout, Vec::new());
     }
-    let id = Id::new("test-area").with(("floating", &5u32)).with("resize");
+    let id = Id::unique("test-area").with(("floating", &5u32)).with("resize");
     ctx.memory_mut(|memory| memory.request_focus(id));
     let key = |key| Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE };
     step(&ctx, &mut layout, vec![key(egui::Key::ArrowRight), key(egui::Key::ArrowDown)]);
@@ -486,7 +486,7 @@ fn stack_dividers_resize_adjacent_open_bodies_by_pointer_and_keyboard() {
     for _ in 0..3 {
         step(&ctx, &mut layout, Vec::new());
     }
-    let id = Id::new("test-area").with(("stack-resize", &1u32));
+    let id = Id::unique("test-area").with(("stack-resize", &1u32));
     let start = ctx.read_response(id).unwrap().rect.center();
     step(&ctx, &mut layout, vec![Event::PointerMoved(start), pointer_button(start, true)]);
     step(&ctx, &mut layout, vec![Event::PointerMoved(start + vec2(0.0, 24.0))]);
@@ -525,7 +525,7 @@ fn split_inner_edge_remains_draggable_beside_a_scrollbar() {
         let mut output = DockOutput::default();
         let mut sidebar = Rect::NOTHING;
         ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), events, ..Default::default() }, |ui| {
-            output = DockArea::new(Id::new("scrolling-dock")).show(
+            output = DockArea::new(Id::unique("scrolling-dock")).show(
                 ui,
                 layout,
                 &DockStyle::from_ui(ui),
@@ -597,7 +597,7 @@ fn floating_only_surface_does_not_allocate_the_callers_document_region() {
     let layout = Layout { root: None, floating: vec![Floating { panels: vec![1u32], active: 0, rect: [30.0, 40.0, 200.0, 200.0] }] };
     ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), ..Default::default() }, |ui| {
         let before = ui.available_rect_before_wrap();
-        let out = DockArea::new(Id::new("float-only")).show(
+        let out = DockArea::new(Id::unique("float-only")).show(
             ui,
             &layout,
             &DockStyle::from_ui(ui),
@@ -633,7 +633,7 @@ fn floating_group_grip_moves_every_member_while_tab_drag_detaches_one() {
     for _ in 0..2 {
         step(&ctx, &mut layout, vec![]);
     }
-    let tab = ctx.read_response(Id::new("test-area").with(("tab", &5u32))).unwrap().rect.center();
+    let tab = ctx.read_response(Id::unique("test-area").with(("tab", &5u32))).unwrap().rect.center();
     step(&ctx, &mut layout, vec![Event::PointerMoved(tab), pointer_button(tab, true)]);
     step(&ctx, &mut layout, vec![Event::PointerMoved(tab + vec2(8.0, 0.0))]);
     step(&ctx, &mut layout, vec![Event::PointerMoved(tab + vec2(100.0, 100.0))]);
@@ -663,7 +663,7 @@ fn host_cancel_discards_group_drag_before_workspace_replacement() {
     let actions = step(&ctx, &mut layout, vec![Event::PointerMoved(moved)]);
     assert!(actions.iter().any(|action| matches!(action, Action::MoveFloating { .. })));
 
-    cancel_drag::<u32>(&ctx, Id::new("test-area"));
+    cancel_drag::<u32>(&ctx, Id::unique("test-area"));
     layout = sample();
     let replacement = layout.clone();
     let actions = step(&ctx, &mut layout, vec![Event::PointerMoved(moved + vec2(20.0, 10.0))]);
@@ -696,7 +696,7 @@ impl DockContent<u32> for CustomContent {
     fn group_header(&mut self, ui: &mut egui::Ui, _: &[u32], _: &u32, _: bool) {
         self.headers += 1;
         assert!(ui.max_rect().width() <= 40.0);
-        ui.label("⋮");
+        ui.label("…");
     }
 }
 
@@ -711,7 +711,7 @@ fn custom_frame(
     let mut out = DockOutput::default();
     ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), events, ..Default::default() }, |ui| {
         let style = DockStyle::from_ui(ui);
-        let mut area = DockArea::new(Id::new("test-area")).blocked_rects(blockers);
+        let mut area = DockArea::new(Id::unique("test-area")).blocked_rects(blockers);
         if raise {
             area = area.raise_panel(5);
         }
@@ -762,7 +762,7 @@ fn overlay_appearing_mid_drag_blocks_release_and_float_fallback() {
     let mut content = CustomContent { labels: vec![], headers: 0, menus: 0, bodies: 0 };
     let source = Rect::from_min_size(pos2(80.0, 70.0), vec2(300.0, 220.0));
     custom_frame(&ctx, &layout, &mut content, vec![Event::PointerMoved(pos2(130.0, 82.0)), pointer_button(pos2(130.0, 82.0), true)], &[], false);
-    assert!(begin_drag(&ctx, Id::new("test-area"), 5, source));
+    assert!(begin_drag(&ctx, Id::unique("test-area"), 5, source));
     let at = pos2(700.0, 400.0);
     let blocker = Rect::from_min_size(pos2(650.0, 350.0), vec2(100.0, 100.0));
     let out = custom_frame(&ctx, &layout, &mut content, vec![Event::PointerMoved(at), pointer_button(at, false)], &[blocker], false);
@@ -801,7 +801,7 @@ fn custom_headers_and_labels_render_at_small_and_large_scales() {
         let mut out = DockOutput::default();
         ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(120.0 * scale, 160.0))), ..Default::default() }, |ui| {
             let style = DockStyle::from_ui(ui);
-            out = DockArea::new(Id::new("scaled")).show_customized(
+            out = DockArea::new(Id::unique("scaled")).show_customized(
                 ui,
                 &layout,
                 &style,
@@ -834,7 +834,7 @@ fn protected_group_member_blocks_whole_group_gesture_and_host_drop() {
         let mut out = DockOutput::default();
         ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), events, ..Default::default() }, |ui| {
             let style = DockStyle::from_ui(ui);
-            out = DockArea::new(Id::new("test-area")).show(
+            out = DockArea::new(Id::unique("test-area")).show(
                 ui,
                 &layout,
                 &style,
@@ -856,13 +856,13 @@ fn protected_group_member_blocks_whole_group_gesture_and_host_drop() {
 fn chrome_focus_classifies_rendered_tab_but_excludes_body_text_field_and_invalid_layout() {
     let ctx = Context::default();
     let layout = sample();
-    let tab = Id::new("focus-area").with(("tab", 3u32));
-    let text = Id::new("body-text");
+    let tab = Id::unique("focus-area").with(("tab", 3u32));
+    let text = Id::unique("body-text");
     let mut value = String::new();
     let mut paint = |focus: Id| {
         let mut output =
             ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(800.0, 600.0))), ..Default::default() }, |ui| {
-                DockArea::new(Id::new("focus-area")).show(
+                DockArea::new(Id::unique("focus-area")).show(
                     ui,
                     &layout,
                     &DockStyle::from_ui(ui),
@@ -879,22 +879,22 @@ fn chrome_focus_classifies_rendered_tab_but_excludes_body_text_field_and_invalid
         output.textures_delta.clear();
     };
     paint(tab);
-    assert!(DockArea::new(Id::new("focus-area")).chrome_has_focus(&ctx, &layout));
+    assert!(DockArea::new(Id::unique("focus-area")).chrome_has_focus(&ctx, &layout));
     paint(text);
-    assert!(!DockArea::new(Id::new("focus-area")).chrome_has_focus(&ctx, &layout));
+    assert!(!DockArea::new(Id::unique("focus-area")).chrome_has_focus(&ctx, &layout));
     paint(tab);
     let mut invalid = layout.clone();
     invalid.floating.push(Floating { panels: vec![3], active: 0, rect: [10.0, 10.0, 100.0, 100.0] });
-    assert!(!DockArea::new(Id::new("focus-area")).chrome_has_focus(&ctx, &invalid));
-    assert!(!DockArea::<u32>::new(Id::new("focus-area")).chrome_has_focus(&ctx, &Layout::default()));
-    ctx.memory_mut(|memory| memory.request_focus(Id::new("unrendered")));
-    assert!(!DockArea::new(Id::new("focus-area")).chrome_has_focus(&ctx, &layout));
+    assert!(!DockArea::new(Id::unique("focus-area")).chrome_has_focus(&ctx, &invalid));
+    assert!(!DockArea::<u32>::new(Id::unique("focus-area")).chrome_has_focus(&ctx, &Layout::default()));
+    ctx.memory_mut(|memory| memory.request_focus(Id::unique("unrendered")));
+    assert!(!DockArea::new(Id::unique("focus-area")).chrome_has_focus(&ctx, &layout));
 }
 
 #[test]
 fn chrome_focus_classifies_rendered_accordion_and_split_and_floating_resizers() {
     let ctx = Context::default();
-    let area = Id::new("focus-chrome-controls");
+    let area = Id::unique("focus-chrome-controls");
     let accordion = Layout {
         root: Some(Node::Stack {
             entries: vec![StackEntry { panel: 1u32, open: true, height: Some(150.0) }, StackEntry { panel: 2u32, open: true, height: Some(150.0) }],
@@ -930,7 +930,7 @@ fn chrome_focus_classifies_rendered_accordion_and_split_and_floating_resizers()
 
 #[test]
 fn workspace_cancel_stops_owned_resize_controls_until_new_press() {
-    let area = Id::new("test-area");
+    let area = Id::unique("test-area");
     let fixtures = [
         (sample(), area.with(("split", Vec::<bool>::new())), vec2(24.0, 0.0)),
         (
@@ -978,8 +978,8 @@ fn workspace_cancel_stops_owned_resize_controls_until_new_press() {
 fn workspace_cancel_preserves_other_area_and_body_drag_ownership() {
     for body_drag in [false, true] {
         let ctx = Context::default();
-        let area = Id::new("cancel-area");
-        let other = Id::new("other-area");
+        let area = Id::unique("cancel-area");
+        let other = Id::unique("other-area");
         let layout = Layout {
             root: Some(Node::Split {
                 axis: SplitAxis::Horizontal,
@@ -989,7 +989,7 @@ fn workspace_cancel_preserves_other_area_and_body_drag_ownership() {
             }),
             floating: vec![],
         };
-        let body_id = Id::new("application-body-drag");
+        let body_id = Id::unique("application-body-drag");
         let mut value = 0.0;
         let mut run = |events: Vec<Event>| {
             let mut outputs = Vec::new();
@@ -1052,7 +1052,7 @@ fn popup_action_labels_use_custom_visual_text_and_logical_accessibility() {
     let layout = sample();
     let mut h = Harness::builder().with_size(vec2(800.0, 600.0)).build_ui_state(
         move |ui, content: &mut CustomContent| {
-            DockArea::new(Id::new("action-label-test")).show_customized(
+            DockArea::new(Id::unique("action-label-test")).show_customized(
                 ui,
                 &layout,
                 &DockStyle::from_ui(ui),
@@ -1065,7 +1065,7 @@ fn popup_action_labels_use_custom_visual_text_and_logical_accessibility() {
         CustomContent { labels: vec![], headers: 0, menus: 0, bodies: 0 },
     );
     h.run_steps(2);
-    let tab = Id::new("action-label-test").with(("tab", &5_u32));
+    let tab = Id::unique("action-label-test").with(("tab", &5_u32));
     let at = h.ctx.read_response(tab).unwrap().rect.center();
     h.input_mut().events.extend([
         Event::PointerMoved(at),
diff --git a/crates/ui/src/layout/resize.rs b/crates/ui/src/layout/resize.rs
--- a/crates/ui/src/layout/resize.rs
+++ b/crates/ui/src/layout/resize.rs
@@ -151,7 +151,7 @@ impl<'a> ResizeHandle<'a> {
             {
                 egui::WidgetInfo::slider(enabled, f64::from(*value), self.label)
             } else {
-                egui::WidgetInfo::labeled(egui::WidgetType::Other, enabled, self.label)
+                egui::WidgetInfo::labeled(egui::Role::Unknown, enabled, self.label)
             }
         });
         ui.ctx().accesskit_node_builder(response.id, |node| {
diff --git a/crates/ui/src/layout/tests.rs b/crates/ui/src/layout/tests.rs
--- a/crates/ui/src/layout/tests.rs
+++ b/crates/ui/src/layout/tests.rs
@@ -127,7 +127,7 @@ impl HandleTest {
             ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(400.0, 200.0))), events, ..Default::default() }, |ui| {
                 result = Some(
                     ui.add_enabled_ui(self.enabled, |ui| {
-                        ResizeHandle::new(Id::new("divider"), self.axis, self.rect, &self.label, handle_style())
+                        ResizeHandle::new(Id::unique("divider"), self.axis, self.rect, &self.label, handle_style())
                             .value(self.value, 0.0..=300.0)
                             .show(ui)
                     })
diff --git a/crates/ui/src/printing/tests.rs b/crates/ui/src/printing/tests.rs
--- a/crates/ui/src/printing/tests.rs
+++ b/crates/ui/src/printing/tests.rs
@@ -99,7 +99,7 @@ fn discovery_and_controls_do_not_submit_a_job() {
         grayscale: "Graustufen",
     };
     let ctx = egui::Context::default();
-    let mut output = ctx.run_ui(egui::RawInput::default(), |ui| state.show(ui, egui::Id::new("print"), &discover, &labels));
+    let mut output = ctx.run_ui(egui::RawInput::default(), |ui| state.show(ui, egui::Id::unique("print"), &discover, &labels));
     output.textures_delta.clear();
     assert_eq!(state.settings.printer, "Office");
     assert!(state.ready());
@@ -134,7 +134,7 @@ fn localized_controls_fit_narrow_ui_and_disable_while_submitting() {
     let frame = |state: &mut State, events| {
         let mut output = ctx.run_ui(
             egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(220.0, 360.0))), events, ..Default::default() },
-            |ui| state.show(ui, egui::Id::new("print"), &discover, &labels),
+            |ui| state.show(ui, egui::Id::unique("print"), &discover, &labels),
         );
         output.textures_delta.clear();
         output
diff --git a/crates/ui/src/tabs/mod.rs b/crates/ui/src/tabs/mod.rs
--- a/crates/ui/src/tabs/mod.rs
+++ b/crates/ui/src/tabs/mod.rs
@@ -139,7 +139,7 @@ impl<'a> Tab<'a> {
         let valid = rect.is_finite() && size.x > 0.0 && size.y > 0.0 && size.x <= MAX_EXTENT && size.y <= MAX_EXTENT;
         let safe_rect = if valid { rect } else { Rect::from_min_size(egui::Pos2::ZERO, Vec2::ZERO) };
         let response = ui.interact(safe_rect, self.id, if valid { self.sense } else { Sense::hover() });
-        response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::SelectableLabel, valid && ui.is_enabled(), self.selected, self.label));
+        response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, valid && ui.is_enabled(), self.selected, self.label));
         ui.ctx().accesskit_node_builder(self.id, |node| {
             node.set_role(egui::accesskit::Role::Tab);
             node.clear_toggled();
diff --git a/crates/ui/src/tabs/tests.rs b/crates/ui/src/tabs/tests.rs
--- a/crates/ui/src/tabs/tests.rs
+++ b/crates/ui/src/tabs/tests.rs
@@ -112,7 +112,7 @@ impl TabTest {
         let mut output = ctx.run_ui(raw, |ui| {
             ui.add_enabled_ui(self.enabled, |ui| {
                 for (i, label) in self.labels.iter().enumerate() {
-                    let r = Tab::new(ui.id().with(("tab", i)), label, self.selected == i)
+                    let r = Tab::new(ui.scope_id().with(("tab", i)), label, self.selected == i)
                         .sense(self.sense)
                         .focus_stroke(Stroke::new(1.0, Color32::LIGHT_BLUE))
                         .show_at(ui, self.rect.translate(vec2(i as f32 * 100.0, 0.0)), |ui, response| {
diff --git a/crates/ui/tests/docking_workflows.rs b/crates/ui/tests/docking_workflows.rs
--- a/crates/ui/tests/docking_workflows.rs
+++ b/crates/ui/tests/docking_workflows.rs
@@ -90,14 +90,14 @@ impl Harness {
         let mut actions = Vec::new();
         self.output = self.ctx.run_ui(input, |ui| {
             let style = DockStyle::from_ui(ui);
-            let out = DockArea::new(Id::new("workflow")).show(
+            let out = DockArea::new(Id::unique("workflow")).show(
                 ui,
                 &self.layout,
                 &style,
                 label,
                 |panel| if *panel == CANVAS { Permissions::PROTECTED } else { Permissions::default() },
                 |ui, panel| {
-                    self.body_ids.insert(*panel, ui.id());
+                    self.body_ids.insert(*panel, ui.scope_id());
                     if *panel == CANVAS {
                         let rect = ui.available_rect_before_wrap();
                         ui.painter().rect_filled(rect, 0.0, Color32::from_rgb(44, 53, 66));
@@ -181,7 +181,7 @@ impl Harness {
     }
 
     fn tab(&self, panel: u32) -> Rect {
-        self.ctx.read_response(Id::new("workflow").with(("tab", &panel))).unwrap().rect
+        self.ctx.read_response(Id::unique("workflow").with(("tab", &panel))).unwrap().rect
     }
 
     fn drag(&mut self, from: Pos2, to: Pos2) {
diff --git a/crates/ui/tests/visual/fixtures.rs b/crates/ui/tests/visual/fixtures.rs
--- a/crates/ui/tests/visual/fixtures.rs
+++ b/crates/ui/tests/visual/fixtures.rs
@@ -221,7 +221,7 @@ pub fn actual(ui: &mut Ui, case: Case, mutation: Mutation) -> Vec<Response> {
         let response = ui
             .add_enabled_ui(case.state.enabled(), |ui| {
                 ResizeHandle::new(
-                    Id::new("visual-divider"),
+                    Id::unique("visual-divider"),
                     axis,
                     split.divider,
                     "Resize panes",
@@ -252,7 +252,7 @@ pub fn actual(ui: &mut Ui, case: Case, mutation: Mutation) -> Vec<Response> {
                     let selected = index == 0 && case.state.selected();
                     let response = if matches!(case.component, Component::Tab) {
                         let rect = Rect::from_min_size(pos2(12.0 + index as f32 * (case.size().x + spacing), 12.0), case.size());
-                        Tab::new(Id::new(("visual-tab", index)), "Tab", selected)
+                        Tab::new(Id::unique(("visual-tab", index)), "Tab", selected)
                             .focus_stroke(style.focus_stroke)
                             .show_at(ui, rect, |ui, r| tab_body(ui, r, selected, p, case.compact))
                     } else {
