commit 09b463c5875c2cefc09569468db1bff6472cfc3b
Author: triesap <tyson@radroots.org>
Date: Mon, 3 Aug 2026 22:45:35 +0000
studio: track native dashboard refactor
- implement the macOS-classic design system and reactive theme behavior
- replace starter routes with the Studio dashboard and workspace flows
- add native Settings, About, and Rust-backed application state
- add governed lifecycle, verification, fonts, icons, and regression coverage
Diffstat:
9 files changed, 1111 insertions(+), 0 deletions(-)
diff --git a/imports/studio_mpl_legacy_core/Cargo.lock b/imports/studio_mpl_legacy_core/Cargo.lock
@@ -0,0 +1,754 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "askama"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc"
+dependencies = [
+ "askama_macros",
+ "itoa",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "askama_derive"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738"
+dependencies = [
+ "askama_parser",
+ "basic-toml",
+ "glob",
+ "memchr",
+ "proc-macro2",
+ "quote",
+ "rustc-hash",
+ "serde",
+ "serde_derive",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "askama_macros"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a"
+dependencies = [
+ "askama_derive",
+]
+
+[[package]]
+name = "askama_parser"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da"
+dependencies = [
+ "rustc-hash",
+ "serde",
+ "serde_derive",
+ "unicode-ident",
+ "winnow",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "basic-toml"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "camino"
+version = "1.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "clap"
+version = "4.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "fs-err"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
+
+[[package]]
+name = "goblin"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "scroll"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"
+dependencies = [
+ "scroll_derive",
+]
+
+[[package]]
+name = "scroll_derive"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "smawk"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "studio_app_core"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "uniffi",
+]
+
+[[package]]
+name = "studio_app_core_uniffi_bindgen"
+version = "0.1.0"
+dependencies = [
+ "uniffi",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+dependencies = [
+ "smawk",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.4+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "uniffi"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a782a48d72cfd7a2d65cfc7c691dbf5375c43104b3c195f7eccc716dcc3540c8"
+dependencies = [
+ "anyhow",
+ "camino",
+ "cargo_metadata",
+ "clap",
+ "uniffi_bindgen",
+ "uniffi_core",
+ "uniffi_macros",
+ "uniffi_pipeline",
+]
+
+[[package]]
+name = "uniffi_bindgen"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533b0312c73e3b54eb78a4b257ceae390962dd4767995778309a74644643f9ac"
+dependencies = [
+ "anyhow",
+ "askama",
+ "camino",
+ "cargo_metadata",
+ "fs-err",
+ "glob",
+ "goblin",
+ "heck",
+ "indexmap",
+ "once_cell",
+ "serde",
+ "tempfile",
+ "textwrap",
+ "toml",
+ "uniffi_internal_macros",
+ "uniffi_meta",
+ "uniffi_pipeline",
+ "uniffi_udl",
+]
+
+[[package]]
+name = "uniffi_core"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e32e261c5b0dfaba6488f536e71957dddd6b1a498ac7eb791bee56b60a086be"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "once_cell",
+ "static_assertions",
+]
+
+[[package]]
+name = "uniffi_internal_macros"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84ae78069a5e6772ef694fd5bdb628532c88d2c2f0e7142bf6a384636eadb1af"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "uniffi_macros"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330be6770532e86320df31f54c70bb0be67588594e8e77fa56e9083a3fed5d0d"
+dependencies = [
+ "camino",
+ "fs-err",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 2.0.119",
+ "toml",
+ "uniffi_meta",
+]
+
+[[package]]
+name = "uniffi_meta"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78de021f5547e56ab16c665a49d67d4fd3d31e77422f7739a2e9359d328cd9e7"
+dependencies = [
+ "anyhow",
+ "siphasher",
+ "uniffi_internal_macros",
+ "uniffi_pipeline",
+]
+
+[[package]]
+name = "uniffi_pipeline"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f8201bb1907ed8a42d80e11cbc25c8a033e7a31c3cff1d911f56eedb81d4948"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "tempfile",
+ "uniffi_internal_macros",
+]
+
+[[package]]
+name = "uniffi_udl"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6e57996bc58009cc29bf04845d627ae313c2547b87171c1c349d6c51a1656c0"
+dependencies = [
+ "anyhow",
+ "textwrap",
+ "uniffi_meta",
+ "weedle2",
+]
+
+[[package]]
+name = "weedle2"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+dependencies = [
+ "memchr",
+]
diff --git a/imports/studio_mpl_legacy_core/Cargo.toml b/imports/studio_mpl_legacy_core/Cargo.toml
@@ -0,0 +1,18 @@
+[workspace]
+members = [
+ "crates/core",
+ "tools/uniffi-bindgen",
+]
+resolver = "2"
+
+[workspace.package]
+edition = "2024"
+license = "MPL-2.0"
+rust-version = "1.97"
+
+[profile.release]
+codegen-units = 1
+lto = "thin"
+opt-level = 3
+panic = "unwind"
+strip = "symbols"
diff --git a/imports/studio_mpl_legacy_core/RADROOTS_PATCH_VERSION b/imports/studio_mpl_legacy_core/RADROOTS_PATCH_VERSION
@@ -0,0 +1 @@
+2026-07-31.2
diff --git a/imports/studio_mpl_legacy_core/crates/core/Cargo.toml b/imports/studio_mpl_legacy_core/crates/core/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "studio_app_core"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
+publish = false
+
+[lib]
+crate-type = ["rlib", "cdylib", "staticlib"]
+
+[dependencies]
+serde = { version = "=1.0.228", features = ["derive"] }
+serde_json = "=1.0.145"
+uniffi = "=0.32.0"
diff --git a/imports/studio_mpl_legacy_core/crates/core/src/lib.rs b/imports/studio_mpl_legacy_core/crates/core/src/lib.rs
@@ -0,0 +1,292 @@
+//! UI-neutral Radroots Studio application core.
+//!
+//! This crate owns the canonical process-global application state. Native UI
+//! shells receive immutable snapshots through UniFFI and submit explicit state
+//! transitions back to Rust. No Compose, AppKit, Swing, browser, or other UI
+//! types cross this boundary.
+
+use serde::{Deserialize, Serialize};
+use std::sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard};
+
+const SCHEMA_VERSION: u32 = 1;
+const SAMPLE_ACCOUNT_ID: &str = "account-tyson-lupul";
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
+pub enum AccountLoginStatus {
+ LoggedIn,
+ LoggedOut,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
+pub enum UpdateChannel {
+ Stable,
+ Preview,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
+pub struct AccountState {
+ pub id: String,
+ pub display_name: String,
+ pub network_name: String,
+ pub email: String,
+ pub login_status: AccountLoginStatus,
+ pub expiry_summary: String,
+ pub avatar_asset_key: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
+pub struct PreferencesState {
+ pub allow_incoming_connections: bool,
+ pub use_radroots_dns: bool,
+ pub use_radroots_subnets: bool,
+ pub launch_at_login: bool,
+ pub hide_dock_icon: bool,
+ pub vpn_on_demand_enabled: bool,
+ pub run_as_exit_node: bool,
+ pub allow_local_network_access: bool,
+ pub automatically_check_for_updates: bool,
+ pub update_channel: UpdateChannel,
+ pub last_update_check_summary: String,
+ pub alternate_server_url: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
+pub struct GlobalAppState {
+ pub schema_version: u32,
+ pub revision: u64,
+ pub accounts: Vec<AccountState>,
+ pub selected_account_id: Option<String>,
+ pub preferences: PreferencesState,
+}
+
+static GLOBAL_APP_STATE: OnceLock<RwLock<GlobalAppState>> = OnceLock::new();
+
+fn initial_state() -> GlobalAppState {
+ GlobalAppState {
+ schema_version: SCHEMA_VERSION,
+ revision: 1,
+ accounts: vec![AccountState {
+ id: SAMPLE_ACCOUNT_ID.to_owned(),
+ display_name: "Tyson Lupul".to_owned(),
+ network_name: "triesap.github".to_owned(),
+ email: "triesap@github".to_owned(),
+ login_status: AccountLoginStatus::LoggedOut,
+ expiry_summary: "Expires in 4 months".to_owned(),
+ avatar_asset_key: "radroots_sample_account_avatar".to_owned(),
+ }],
+ selected_account_id: Some(SAMPLE_ACCOUNT_ID.to_owned()),
+ preferences: PreferencesState {
+ allow_incoming_connections: true,
+ use_radroots_dns: true,
+ use_radroots_subnets: true,
+ launch_at_login: true,
+ hide_dock_icon: false,
+ vpn_on_demand_enabled: false,
+ run_as_exit_node: false,
+ allow_local_network_access: false,
+ automatically_check_for_updates: true,
+ update_channel: UpdateChannel::Stable,
+ last_update_check_summary: "Today, 20:31".to_owned(),
+ alternate_server_url: String::new(),
+ },
+ }
+}
+
+fn state_lock() -> &'static RwLock<GlobalAppState> {
+ GLOBAL_APP_STATE.get_or_init(|| RwLock::new(initial_state()))
+}
+
+fn read_state() -> RwLockReadGuard<'static, GlobalAppState> {
+ state_lock()
+ .read()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
+fn write_state() -> RwLockWriteGuard<'static, GlobalAppState> {
+ state_lock()
+ .write()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
+/// Applies a transition to a candidate snapshot, then commits it atomically.
+///
+/// This prevents partially-applied state if a future transition grows to touch
+/// multiple fields. Revisions advance only when the canonical value changes.
+fn commit_if_changed(
+ state: &mut GlobalAppState,
+ mutate: impl FnOnce(&mut GlobalAppState),
+) -> GlobalAppState {
+ let before = state.clone();
+ let mut candidate = before.clone();
+ mutate(&mut candidate);
+
+ if candidate != before {
+ candidate.revision = before.revision.saturating_add(1);
+ *state = candidate;
+ }
+
+ state.clone()
+}
+
+#[uniffi::export]
+pub fn initialize_global_app_state() -> GlobalAppState {
+ state_lock();
+ read_state().clone()
+}
+
+#[uniffi::export]
+pub fn read_global_app_state() -> GlobalAppState {
+ read_state().clone()
+}
+
+#[uniffi::export]
+pub fn export_global_app_state_json() -> String {
+ serde_json::to_string_pretty(&*read_state()).unwrap_or_else(|error| {
+ serde_json::json!({
+ "error": format!("failed to serialize global app state: {error}"),
+ })
+ .to_string()
+ })
+}
+
+#[uniffi::export]
+pub fn select_account(account_id: String) -> GlobalAppState {
+ let mut state = write_state();
+ commit_if_changed(&mut state, |candidate| {
+ if candidate
+ .accounts
+ .iter()
+ .any(|account| account.id == account_id)
+ {
+ candidate.selected_account_id = Some(account_id);
+ }
+ })
+}
+
+#[uniffi::export]
+pub fn set_selected_account_login_status(status: AccountLoginStatus) -> GlobalAppState {
+ let mut state = write_state();
+ commit_if_changed(&mut state, |candidate| {
+ let Some(selected_id) = candidate.selected_account_id.clone() else {
+ return;
+ };
+ if let Some(account) = candidate
+ .accounts
+ .iter_mut()
+ .find(|account| account.id == selected_id)
+ {
+ account.login_status = status;
+ }
+ })
+}
+
+#[uniffi::export]
+pub fn remove_selected_account() -> GlobalAppState {
+ let mut state = write_state();
+ commit_if_changed(&mut state, |candidate| {
+ let Some(selected_id) = candidate.selected_account_id.take() else {
+ return;
+ };
+ candidate
+ .accounts
+ .retain(|account| account.id != selected_id);
+ candidate.selected_account_id =
+ candidate.accounts.first().map(|account| account.id.clone());
+ })
+}
+
+#[uniffi::export]
+pub fn set_allow_incoming_connections(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.allow_incoming_connections = value)
+}
+
+#[uniffi::export]
+pub fn set_use_radroots_dns(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.use_radroots_dns = value)
+}
+
+#[uniffi::export]
+pub fn set_use_radroots_subnets(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.use_radroots_subnets = value)
+}
+
+#[uniffi::export]
+pub fn set_launch_at_login(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.launch_at_login = value)
+}
+
+#[uniffi::export]
+pub fn set_hide_dock_icon(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.hide_dock_icon = value)
+}
+
+#[uniffi::export]
+pub fn set_vpn_on_demand_enabled(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.vpn_on_demand_enabled = value)
+}
+
+#[uniffi::export]
+pub fn set_run_as_exit_node(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.run_as_exit_node = value)
+}
+
+#[uniffi::export]
+pub fn set_allow_local_network_access(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.allow_local_network_access = value)
+}
+
+#[uniffi::export]
+pub fn set_automatically_check_for_updates(value: bool) -> GlobalAppState {
+ update_preferences(|preferences| preferences.automatically_check_for_updates = value)
+}
+
+#[uniffi::export]
+pub fn set_update_channel(value: UpdateChannel) -> GlobalAppState {
+ update_preferences(|preferences| preferences.update_channel = value)
+}
+
+#[uniffi::export]
+pub fn set_alternate_server_url(value: String) -> GlobalAppState {
+ update_preferences(|preferences| preferences.alternate_server_url = value.trim().to_owned())
+}
+
+fn update_preferences(mutate: impl FnOnce(&mut PreferencesState)) -> GlobalAppState {
+ let mut state = write_state();
+ commit_if_changed(&mut state, |candidate| {
+ mutate(&mut candidate.preferences);
+ })
+}
+
+uniffi::setup_scaffolding!();
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn initial_state_contains_the_reviewed_account_fixture() {
+ let state = initial_state();
+ let account = state.accounts.first().expect("sample account");
+ assert_eq!(account.display_name, "Tyson Lupul");
+ assert_eq!(account.network_name, "triesap.github");
+ assert_eq!(account.email, "triesap@github");
+ assert_eq!(account.login_status, AccountLoginStatus::LoggedOut);
+ assert_eq!(
+ state.selected_account_id.as_deref(),
+ Some(SAMPLE_ACCOUNT_ID)
+ );
+ }
+
+ #[test]
+ fn candidate_commit_increments_revision_only_when_state_changes() {
+ let mut state = initial_state();
+ let first_revision = state.revision;
+ let unchanged = commit_if_changed(&mut state, |_| {});
+ assert_eq!(unchanged.revision, first_revision);
+
+ let changed = commit_if_changed(&mut state, |candidate| {
+ candidate.preferences.hide_dock_icon = true;
+ });
+ assert_eq!(changed.revision, first_revision + 1);
+ }
+}
diff --git a/imports/studio_mpl_legacy_core/crates/core/uniffi.toml b/imports/studio_mpl_legacy_core/crates/core/uniffi.toml
@@ -0,0 +1,10 @@
+[bindings.kotlin]
+package_name = "com.radroots.studio.coreffi"
+cdylib_name = "studio_app_core"
+generate_immutable_records = true
+kotlin_target_version = "2.4.10"
+
+[bindings.swift]
+module_name = "StudioAppCore"
+cdylib_name = "studio_app_core"
+generate_immutable_records = true
diff --git a/imports/studio_mpl_legacy_core/rust-toolchain.toml b/imports/studio_mpl_legacy_core/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.97.0"
+components = ["clippy", "rustfmt"]
+profile = "minimal"
diff --git a/imports/studio_mpl_legacy_core/tools/uniffi-bindgen/Cargo.toml b/imports/studio_mpl_legacy_core/tools/uniffi-bindgen/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "studio_app_core_uniffi_bindgen"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
+publish = false
+
+[[bin]]
+name = "studio-app-core-uniffi-bindgen"
+path = "src/main.rs"
+
+[dependencies]
+uniffi = { version = "=0.32.0", features = ["cli"] }
diff --git a/imports/studio_mpl_legacy_core/tools/uniffi-bindgen/src/main.rs b/imports/studio_mpl_legacy_core/tools/uniffi-bindgen/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ uniffi::uniffi_bindgen_main();
+}